initial commit
This commit is contained in:
257
libraries/FastLED/src/CMakeLists.txt
Normal file
257
libraries/FastLED/src/CMakeLists.txt
Normal file
@@ -0,0 +1,257 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Efficiently compiles the libfastled.a archive to link against.
|
||||
# Optionally, you can copy the header tree to a specified include path.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# Set FastLED source directory (this is where the FastLED sources live)
|
||||
set(FASTLED_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
message(STATUS "FASTLED_SOURCE_DIR: ${FASTLED_SOURCE_DIR}")
|
||||
|
||||
if(NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
message(STATUS "CMAKE_CXX_STANDARD not defined. Setting C++ standard to 17.")
|
||||
else()
|
||||
message(STATUS "CMAKE_CXX_STANDARD already defined as: ${CMAKE_CXX_STANDARD}")
|
||||
endif()
|
||||
|
||||
# Retrieve and print the flags passed from the parent build system
|
||||
message(STATUS "Using compile flags from parent CMakeLists.txt")
|
||||
message(STATUS "COMMON_COMPILE_FLAGS: ${COMMON_COMPILE_FLAGS}")
|
||||
message(STATUS "COMMON_COMPILE_DEFINITIONS: ${COMMON_COMPILE_DEFINITIONS}")
|
||||
|
||||
# Verify the directory exists
|
||||
if(NOT EXISTS ${FASTLED_SOURCE_DIR})
|
||||
message(FATAL_ERROR "Error: FASTLED_SOURCE_DIR does not exist! Check directory path.")
|
||||
endif()
|
||||
|
||||
# Include FastLED headers (assumed to be in this directory)
|
||||
include_directories(${FASTLED_SOURCE_DIR})
|
||||
|
||||
# Check if we should use unified compilation mode (FASTLED_ALL_SRC=1)
|
||||
# Responds to directives from test system or environment variables
|
||||
# Does NOT make compiler-specific decisions - that's handled by the test system
|
||||
if(NOT DEFINED FASTLED_ALL_SRC)
|
||||
# Only set default if not already specified by parent or environment
|
||||
option(FASTLED_ALL_SRC "Enable unified compilation mode" OFF)
|
||||
message(STATUS "FASTLED_ALL_SRC not specified: using default OFF (individual file compilation)")
|
||||
else()
|
||||
# Respect the value passed from parent CMake or environment
|
||||
message(STATUS "FASTLED_ALL_SRC specified by parent/environment: ${FASTLED_ALL_SRC}")
|
||||
endif()
|
||||
|
||||
if(FASTLED_ALL_SRC)
|
||||
message(STATUS "FASTLED_ALL_SRC=ON: Using unified compilation mode")
|
||||
|
||||
# === Get all the source files ===
|
||||
file(GLOB_RECURSE ALL_CPP_FILES "${FASTLED_SOURCE_DIR}/*.cpp")
|
||||
|
||||
# Exclude platform-specific files (e.g. esp, arm, avr)
|
||||
list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*esp.*")
|
||||
list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*arm.*")
|
||||
list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*avr.*")
|
||||
|
||||
list(LENGTH ALL_CPP_FILES CPP_FILE_COUNT)
|
||||
message(STATUS "Found ${CPP_FILE_COUNT} .cpp files for unified compilation")
|
||||
|
||||
# Organize files by subdirectory for detailed reporting
|
||||
set(ROOT_FILES "")
|
||||
set(FL_FILES "")
|
||||
set(FX_FILES "")
|
||||
set(SENSORS_FILES "")
|
||||
set(PLATFORMS_FILES "")
|
||||
set(THIRD_PARTY_FILES "")
|
||||
set(OTHER_FILES "")
|
||||
|
||||
foreach(cpp_file ${ALL_CPP_FILES})
|
||||
file(RELATIVE_PATH relative_path ${FASTLED_SOURCE_DIR} ${cpp_file})
|
||||
|
||||
if(relative_path MATCHES "^fl/")
|
||||
list(APPEND FL_FILES ${relative_path})
|
||||
elseif(relative_path MATCHES "^fx/")
|
||||
list(APPEND FX_FILES ${relative_path})
|
||||
elseif(relative_path MATCHES "^sensors/")
|
||||
list(APPEND SENSORS_FILES ${relative_path})
|
||||
elseif(relative_path MATCHES "^platforms/")
|
||||
list(APPEND PLATFORMS_FILES ${relative_path})
|
||||
elseif(relative_path MATCHES "^third_party/")
|
||||
list(APPEND THIRD_PARTY_FILES ${relative_path})
|
||||
elseif(NOT relative_path MATCHES "/")
|
||||
list(APPEND ROOT_FILES ${relative_path})
|
||||
else()
|
||||
list(APPEND OTHER_FILES ${relative_path})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Report files by category
|
||||
message(STATUS "=== UNIFIED COMPILATION FILE BREAKDOWN ===")
|
||||
|
||||
list(LENGTH ROOT_FILES ROOT_COUNT)
|
||||
if(ROOT_COUNT GREATER 0)
|
||||
message(STATUS "Root src/ files (${ROOT_COUNT}):")
|
||||
foreach(file ${ROOT_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH FL_FILES FL_COUNT)
|
||||
if(FL_COUNT GREATER 0)
|
||||
message(STATUS "FL library files (${FL_COUNT}):")
|
||||
foreach(file ${FL_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH FX_FILES FX_COUNT)
|
||||
if(FX_COUNT GREATER 0)
|
||||
message(STATUS "FX library files (${FX_COUNT}):")
|
||||
foreach(file ${FX_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH SENSORS_FILES SENSORS_COUNT)
|
||||
if(SENSORS_COUNT GREATER 0)
|
||||
message(STATUS "Sensors library files (${SENSORS_COUNT}):")
|
||||
foreach(file ${SENSORS_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH PLATFORMS_FILES PLATFORMS_COUNT)
|
||||
if(PLATFORMS_COUNT GREATER 0)
|
||||
message(STATUS "Platforms library files (${PLATFORMS_COUNT}):")
|
||||
foreach(file ${PLATFORMS_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH THIRD_PARTY_FILES THIRD_PARTY_COUNT)
|
||||
if(THIRD_PARTY_COUNT GREATER 0)
|
||||
message(STATUS "Third party library files (${THIRD_PARTY_COUNT}):")
|
||||
foreach(file ${THIRD_PARTY_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
list(LENGTH OTHER_FILES OTHER_COUNT)
|
||||
if(OTHER_COUNT GREATER 0)
|
||||
message(STATUS "Other files (${OTHER_COUNT}):")
|
||||
foreach(file ${OTHER_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
message(STATUS "=== TOTAL: ${CPP_FILE_COUNT} files combined into unified compilation ===")
|
||||
|
||||
# Generate unified source file that includes all .cpp files
|
||||
set(UNIFIED_SOURCE_FILE "${CMAKE_CURRENT_BINARY_DIR}/fastled_unified.cpp")
|
||||
|
||||
# Create content for unified source file
|
||||
set(UNIFIED_CONTENT "// Auto-generated unified compilation file\n")
|
||||
set(UNIFIED_CONTENT "${UNIFIED_CONTENT}// This file includes all FastLED source files for unified compilation\n")
|
||||
set(UNIFIED_CONTENT "${UNIFIED_CONTENT}// Generated from ${CPP_FILE_COUNT} .cpp files\n\n")
|
||||
|
||||
foreach(cpp_file ${ALL_CPP_FILES})
|
||||
# Convert absolute path to relative path from FASTLED_SOURCE_DIR
|
||||
file(RELATIVE_PATH relative_path ${FASTLED_SOURCE_DIR} ${cpp_file})
|
||||
set(UNIFIED_CONTENT "${UNIFIED_CONTENT}#include \"${relative_path}\"\n")
|
||||
endforeach()
|
||||
|
||||
# Write the unified source file
|
||||
file(WRITE ${UNIFIED_SOURCE_FILE} ${UNIFIED_CONTENT})
|
||||
|
||||
# Use only the unified source file for compilation
|
||||
set(FASTLED_SOURCES ${UNIFIED_SOURCE_FILE})
|
||||
|
||||
message(STATUS "Generated unified source file: ${UNIFIED_SOURCE_FILE}")
|
||||
|
||||
else()
|
||||
message(STATUS "FASTLED_ALL_SRC=OFF: Using individual file compilation mode")
|
||||
|
||||
# === Get all the source files ===
|
||||
file(GLOB_RECURSE FASTLED_SOURCES "${FASTLED_SOURCE_DIR}/*.c*")
|
||||
message(STATUS "Found source files: ${FASTLED_SOURCES}")
|
||||
|
||||
if(FASTLED_SOURCES STREQUAL "")
|
||||
message(FATAL_ERROR "Error: No source files found in ${FASTLED_SOURCE_DIR}!")
|
||||
endif()
|
||||
|
||||
# Exclude platform-specific files (e.g. esp, arm, avr)
|
||||
list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*esp.*")
|
||||
list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*arm.*")
|
||||
list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*avr.*")
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Create the main FastLED library from sources (unified or individual)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
add_library(fastled STATIC ${FASTLED_SOURCES})
|
||||
|
||||
# Apply FastLED library-specific compile flags and definitions
|
||||
# Note: We apply library-specific flags that disable exceptions/RTTI for embedded compatibility
|
||||
target_compile_options(fastled PRIVATE
|
||||
# Core warning flags
|
||||
-Wall
|
||||
-funwind-tables
|
||||
$<$<CONFIG:Debug>:-g>
|
||||
$<$<CONFIG:Release>:-O2>
|
||||
# FastLED embedded requirements: disable exceptions and RTTI
|
||||
-fno-exceptions # Disable C++ exceptions (embedded requirement)
|
||||
-fno-rtti # Disable runtime type info (embedded requirement)
|
||||
# Dead code elimination
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
# Essential warnings
|
||||
-Werror=return-type
|
||||
-Werror=missing-declarations
|
||||
-Werror=uninitialized
|
||||
-Werror=array-bounds
|
||||
-Werror=null-dereference
|
||||
-Werror=deprecated-declarations
|
||||
-Wno-comment
|
||||
)
|
||||
|
||||
# Add GCC-specific flags for FastLED library
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options(fastled PRIVATE
|
||||
-Werror=maybe-uninitialized
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add C++ specific flags for FastLED library
|
||||
target_compile_options(fastled PRIVATE $<$<COMPILE_LANGUAGE:CXX>:
|
||||
-Werror=suggest-override
|
||||
-Werror=non-virtual-dtor
|
||||
-Werror=switch-enum
|
||||
-Werror=delete-non-virtual-dtor
|
||||
>)
|
||||
|
||||
# Apply FastLED library-specific definitions
|
||||
target_compile_definitions(fastled PRIVATE
|
||||
FASTLED_FORCE_NAMESPACE=1
|
||||
FASTLED_NO_AUTO_NAMESPACE
|
||||
FASTLED_STUB_IMPL
|
||||
FASTLED_NO_PINMAP
|
||||
HAS_HARDWARE_PIN_SUPPORT
|
||||
PROGMEM=
|
||||
FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8
|
||||
)
|
||||
|
||||
# Ensure full archive linking: force inclusion of all object files
|
||||
target_link_options(fastled PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive")
|
||||
|
||||
# Add Windows debugging libraries for crash handler
|
||||
if(WIN32)
|
||||
target_link_libraries(fastled dbghelp psapi)
|
||||
endif()
|
||||
|
||||
list(LENGTH FASTLED_SOURCES FASTLED_SOURCE_COUNT)
|
||||
if(FASTLED_ALL_SRC)
|
||||
message(STATUS "Created fastled library with unified compilation (${CPP_FILE_COUNT} .cpp files combined into ${FASTLED_SOURCE_COUNT} compilation unit)")
|
||||
else()
|
||||
message(STATUS "Created fastled library with ${FASTLED_SOURCE_COUNT} individual source files")
|
||||
endif()
|
||||
426
libraries/FastLED/src/FastLED.cpp
Normal file
426
libraries/FastLED/src/FastLED.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
/// @file FastLED.cpp
|
||||
/// Central source file for FastLED, implements the CFastLED class/object
|
||||
|
||||
#ifndef MAX_CLED_CONTROLLERS
|
||||
#ifdef __AVR__
|
||||
// if mega or leonardo, allow more controllers
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__)
|
||||
#define MAX_CLED_CONTROLLERS 16
|
||||
#else
|
||||
#define MAX_CLED_CONTROLLERS 8
|
||||
#endif
|
||||
#else
|
||||
#define MAX_CLED_CONTROLLERS 64
|
||||
#endif // __AVR__
|
||||
#endif // MAX_CLED_CONTROLLERS
|
||||
|
||||
#ifndef FASTLED_MANUAL_ENGINE_EVENTS
|
||||
#define FASTLED_MANUAL_ENGINE_EVENTS 0
|
||||
#endif
|
||||
|
||||
#if defined(__SAM3X8E__)
|
||||
volatile fl::u32 fuckit;
|
||||
#endif
|
||||
|
||||
// Disable to fix build breakage.
|
||||
// #ifndef FASTLED_DEFINE_WEAK_YEILD_FUNCTION
|
||||
// #if defined(__AVR_ATtiny13__)
|
||||
// // Arduino.h also defines this as a weak function on this platform.
|
||||
// #define FASTLED_DEFINE_WEAK_YEILD_FUNCTION 0
|
||||
// #else
|
||||
// #define FASTLED_DEFINE_WEAK_YEILD_FUNCTION 1
|
||||
// #endif
|
||||
// #endif
|
||||
|
||||
/// Has to be declared outside of any namespaces.
|
||||
/// Called at program exit when run in a desktop environment.
|
||||
/// Extra C definition that some environments may need.
|
||||
/// @returns 0 to indicate success
|
||||
|
||||
#ifndef FASTLED_NO_ATEXIT
|
||||
#define FASTLED_NO_ATEXIT 0
|
||||
#endif
|
||||
|
||||
#if !FASTLED_NO_ATEXIT
|
||||
extern "C" FL_LINK_WEAK int atexit(void (* /*func*/ )()) { return 0; }
|
||||
#endif
|
||||
|
||||
#ifdef FASTLED_NEEDS_YIELD
|
||||
extern "C" void yield(void) { }
|
||||
#endif
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
fl::u16 cled_contoller_size() {
|
||||
return sizeof(CLEDController);
|
||||
}
|
||||
|
||||
uint8_t get_brightness();
|
||||
|
||||
/// Pointer to the matrix object when using the Smart Matrix Library
|
||||
/// @see https://github.com/pixelmatix/SmartMatrix
|
||||
void *pSmartMatrix = NULL;
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
|
||||
CFastLED FastLED; // global constructor allowed in this case.
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
CLEDController *CLEDController::m_pHead = NULL;
|
||||
CLEDController *CLEDController::m_pTail = NULL;
|
||||
static fl::u32 lastshow = 0;
|
||||
|
||||
/// Global frame counter, used for debugging ESP implementations
|
||||
/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block?
|
||||
fl::u32 _frame_cnt=0;
|
||||
|
||||
/// Global frame retry counter, used for debugging ESP implementations
|
||||
/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block?
|
||||
fl::u32 _retry_cnt=0;
|
||||
|
||||
// uint32_t CRGB::Squant = ((uint32_t)((__TIME__[4]-'0') * 28))<<16 | ((__TIME__[6]-'0')*50)<<8 | ((__TIME__[7]-'0')*28);
|
||||
|
||||
CFastLED::CFastLED() {
|
||||
// clear out the array of led controllers
|
||||
// m_nControllers = 0;
|
||||
m_Scale = 255;
|
||||
m_nFPS = 0;
|
||||
m_pPowerFunc = NULL;
|
||||
m_nPowerData = 0xFFFFFFFF;
|
||||
m_nMinMicros = 0;
|
||||
}
|
||||
|
||||
int CFastLED::size() {
|
||||
return (*this)[0].size();
|
||||
}
|
||||
|
||||
CRGB* CFastLED::leds() {
|
||||
return (*this)[0].leds();
|
||||
}
|
||||
|
||||
CLEDController &CFastLED::addLeds(CLEDController *pLed,
|
||||
struct CRGB *data,
|
||||
int nLedsOrOffset, int nLedsIfOffset) {
|
||||
int nOffset = (nLedsIfOffset > 0) ? nLedsOrOffset : 0;
|
||||
int nLeds = (nLedsIfOffset > 0) ? nLedsIfOffset : nLedsOrOffset;
|
||||
|
||||
pLed->init();
|
||||
pLed->setLeds(data + nOffset, nLeds);
|
||||
FastLED.setMaxRefreshRate(pLed->getMaxRefreshRate(),true);
|
||||
fl::EngineEvents::onStripAdded(pLed, nLedsOrOffset - nOffset);
|
||||
return *pLed;
|
||||
}
|
||||
|
||||
// This is bad code. But it produces the smallest binaries for reasons
|
||||
// beyond mortal comprehensions.
|
||||
// Instead of iterating through the link list for onBeginFrame(), beginShowLeds()
|
||||
// and endShowLeds(): store the pointers in an array and iterate through that.
|
||||
//
|
||||
// static uninitialized gControllersData produces the smallest binary on attiny85.
|
||||
static void* gControllersData[MAX_CLED_CONTROLLERS];
|
||||
|
||||
void CFastLED::show(uint8_t scale) {
|
||||
#if !FASTLED_MANUAL_ENGINE_EVENTS
|
||||
fl::EngineEvents::onBeginFrame();
|
||||
#endif
|
||||
while(m_nMinMicros && ((micros()-lastshow) < m_nMinMicros));
|
||||
lastshow = micros();
|
||||
|
||||
// If we have a function for computing power, use it!
|
||||
if(m_pPowerFunc) {
|
||||
scale = (*m_pPowerFunc)(scale, m_nPowerData);
|
||||
}
|
||||
|
||||
|
||||
int length = 0;
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
|
||||
while(pCur && length < MAX_CLED_CONTROLLERS) {
|
||||
if (pCur->getEnabled()) {
|
||||
gControllersData[length] = pCur->beginShowLeds(pCur->size());
|
||||
} else {
|
||||
gControllersData[length] = nullptr;
|
||||
}
|
||||
length++;
|
||||
if (m_nFPS < 100) { pCur->setDither(0); }
|
||||
pCur = pCur->next();
|
||||
}
|
||||
|
||||
pCur = CLEDController::head();
|
||||
for (length = 0; length < MAX_CLED_CONTROLLERS && pCur; length++) {
|
||||
if (pCur->getEnabled()) {
|
||||
pCur->showLedsInternal(scale);
|
||||
}
|
||||
pCur = pCur->next();
|
||||
|
||||
}
|
||||
|
||||
length = 0; // Reset length to 0 and iterate again.
|
||||
pCur = CLEDController::head();
|
||||
while(pCur && length < MAX_CLED_CONTROLLERS) {
|
||||
if (pCur->getEnabled()) {
|
||||
pCur->endShowLeds(gControllersData[length]);
|
||||
}
|
||||
length++;
|
||||
pCur = pCur->next();
|
||||
}
|
||||
countFPS();
|
||||
onEndFrame();
|
||||
#if !FASTLED_MANUAL_ENGINE_EVENTS
|
||||
fl::EngineEvents::onEndShowLeds();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CFastLED::onEndFrame() {
|
||||
fl::EngineEvents::onEndFrame();
|
||||
}
|
||||
|
||||
int CFastLED::count() {
|
||||
int x = 0;
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while( pCur) {
|
||||
++x;
|
||||
pCur = pCur->next();
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
CLEDController & CFastLED::operator[](int x) {
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(x-- && pCur) {
|
||||
pCur = pCur->next();
|
||||
}
|
||||
if(pCur == NULL) {
|
||||
return *(CLEDController::head());
|
||||
} else {
|
||||
return *pCur;
|
||||
}
|
||||
}
|
||||
|
||||
void CFastLED::showColor(const struct CRGB & color, uint8_t scale) {
|
||||
while(m_nMinMicros && ((micros()-lastshow) < m_nMinMicros));
|
||||
lastshow = micros();
|
||||
|
||||
// If we have a function for computing power, use it!
|
||||
if(m_pPowerFunc) {
|
||||
scale = (*m_pPowerFunc)(scale, m_nPowerData);
|
||||
}
|
||||
|
||||
int length = 0;
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(pCur && length < MAX_CLED_CONTROLLERS) {
|
||||
if (pCur->getEnabled()) {
|
||||
gControllersData[length] = pCur->beginShowLeds(pCur->size());
|
||||
} else {
|
||||
gControllersData[length] = nullptr;
|
||||
}
|
||||
length++;
|
||||
pCur = pCur->next();
|
||||
}
|
||||
|
||||
pCur = CLEDController::head();
|
||||
while(pCur && length < MAX_CLED_CONTROLLERS) {
|
||||
if(m_nFPS < 100) { pCur->setDither(0); }
|
||||
if (pCur->getEnabled()) {
|
||||
pCur->showColorInternal(color, scale);
|
||||
}
|
||||
pCur = pCur->next();
|
||||
}
|
||||
|
||||
pCur = CLEDController::head();
|
||||
length = 0; // Reset length to 0 and iterate again.
|
||||
while(pCur && length < MAX_CLED_CONTROLLERS) {
|
||||
if (pCur->getEnabled()) {
|
||||
pCur->endShowLeds(gControllersData[length]);
|
||||
}
|
||||
length++;
|
||||
pCur = pCur->next();
|
||||
}
|
||||
countFPS();
|
||||
onEndFrame();
|
||||
}
|
||||
|
||||
void CFastLED::clear(bool writeData) {
|
||||
if(writeData) {
|
||||
showColor(CRGB(0,0,0), 0);
|
||||
}
|
||||
clearData();
|
||||
}
|
||||
|
||||
void CFastLED::clearData() {
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(pCur) {
|
||||
pCur->clearLedDataInternal();
|
||||
pCur = pCur->next();
|
||||
}
|
||||
}
|
||||
|
||||
void CFastLED::delay(unsigned long ms) {
|
||||
unsigned long start = millis();
|
||||
do {
|
||||
#ifndef FASTLED_ACCURATE_CLOCK
|
||||
// make sure to allow at least one ms to pass to ensure the clock moves
|
||||
// forward
|
||||
::delay(1);
|
||||
#endif
|
||||
show();
|
||||
yield();
|
||||
}
|
||||
while((millis()-start) < ms);
|
||||
}
|
||||
|
||||
void CFastLED::setTemperature(const struct CRGB & temp) {
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(pCur) {
|
||||
pCur->setTemperature(temp);
|
||||
pCur = pCur->next();
|
||||
}
|
||||
}
|
||||
|
||||
void CFastLED::setCorrection(const struct CRGB & correction) {
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(pCur) {
|
||||
pCur->setCorrection(correction);
|
||||
pCur = pCur->next();
|
||||
}
|
||||
}
|
||||
|
||||
void CFastLED::setDither(uint8_t ditherMode) {
|
||||
CLEDController *pCur = CLEDController::head();
|
||||
while(pCur) {
|
||||
pCur->setDither(ditherMode);
|
||||
pCur = pCur->next();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// template<int m, int n> void transpose8(unsigned char A[8], unsigned char B[8]) {
|
||||
// uint32_t x, y, t;
|
||||
//
|
||||
// // Load the array and pack it into x and y.
|
||||
// y = *(unsigned int*)(A);
|
||||
// x = *(unsigned int*)(A+4);
|
||||
//
|
||||
// // x = (A[0]<<24) | (A[m]<<16) | (A[2*m]<<8) | A[3*m];
|
||||
// // y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m];
|
||||
//
|
||||
// // pre-transform x
|
||||
// t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
|
||||
// t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
|
||||
//
|
||||
// // pre-transform y
|
||||
// t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
|
||||
// t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
|
||||
//
|
||||
// // final transform
|
||||
// t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
|
||||
// y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
|
||||
// x = t;
|
||||
//
|
||||
// B[7*n] = y; y >>= 8;
|
||||
// B[6*n] = y; y >>= 8;
|
||||
// B[5*n] = y; y >>= 8;
|
||||
// B[4*n] = y;
|
||||
//
|
||||
// B[3*n] = x; x >>= 8;
|
||||
// B[2*n] = x; x >>= 8;
|
||||
// B[n] = x; x >>= 8;
|
||||
// B[0] = x;
|
||||
// // B[0]=x>>24; B[n]=x>>16; B[2*n]=x>>8; B[3*n]=x>>0;
|
||||
// // B[4*n]=y>>24; B[5*n]=y>>16; B[6*n]=y>>8; B[7*n]=y>>0;
|
||||
// }
|
||||
//
|
||||
// void transposeLines(Lines & out, Lines & in) {
|
||||
// transpose8<1,2>(in.bytes, out.bytes);
|
||||
// transpose8<1,2>(in.bytes + 8, out.bytes + 1);
|
||||
// }
|
||||
|
||||
|
||||
/// Unused value
|
||||
/// @todo Remove?
|
||||
extern int noise_min;
|
||||
|
||||
/// Unused value
|
||||
/// @todo Remove?
|
||||
extern int noise_max;
|
||||
|
||||
void CFastLED::countFPS(int nFrames) {
|
||||
static int br = 0;
|
||||
static fl::u32 lastframe = 0; // millis();
|
||||
|
||||
if(br++ >= nFrames) {
|
||||
fl::u32 now = millis();
|
||||
now -= lastframe;
|
||||
if(now == 0) {
|
||||
now = 1; // prevent division by zero below
|
||||
}
|
||||
m_nFPS = (br * 1000) / now;
|
||||
br = 0;
|
||||
lastframe = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void CFastLED::setMaxRefreshRate(fl::u16 refresh, bool constrain) {
|
||||
if(constrain) {
|
||||
// if we're constraining, the new value of m_nMinMicros _must_ be higher than previously (because we're only
|
||||
// allowed to slow things down if constraining)
|
||||
if(refresh > 0) {
|
||||
m_nMinMicros = ((1000000 / refresh) > m_nMinMicros) ? (1000000 / refresh) : m_nMinMicros;
|
||||
}
|
||||
} else if(refresh > 0) {
|
||||
m_nMinMicros = 1000000 / refresh;
|
||||
} else {
|
||||
m_nMinMicros = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t get_brightness() {
|
||||
return FastLED.getBrightness();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef NEED_CXX_BITS
|
||||
namespace __cxxabiv1
|
||||
{
|
||||
#if !defined(ESP8266) && !defined(ESP32)
|
||||
extern "C" void __cxa_pure_virtual (void) {}
|
||||
#endif
|
||||
|
||||
/* guard variables */
|
||||
|
||||
/* The ABI requires a 64-bit type. */
|
||||
__extension__ typedef int __guard __attribute__((mode(__DI__)));
|
||||
|
||||
extern "C" int __cxa_guard_acquire (__guard *) FL_LINK_WEAK;
|
||||
extern "C" void __cxa_guard_release (__guard *) FL_LINK_WEAK;
|
||||
extern "C" void __cxa_guard_abort (__guard *) FL_LINK_WEAK;
|
||||
|
||||
extern "C" int __cxa_guard_acquire (__guard *g)
|
||||
{
|
||||
return !*(char *)(g);
|
||||
}
|
||||
|
||||
extern "C" void __cxa_guard_release (__guard *g)
|
||||
{
|
||||
*(char *)g = 1;
|
||||
}
|
||||
|
||||
extern "C" void __cxa_guard_abort (__guard *)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
1017
libraries/FastLED/src/FastLED.h
Normal file
1017
libraries/FastLED/src/FastLED.h
Normal file
File diff suppressed because it is too large
Load Diff
254
libraries/FastLED/src/README.md
Normal file
254
libraries/FastLED/src/README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# FastLED Source Tree (`src/`)
|
||||
|
||||
This document introduces the FastLED source tree housed under `src/`, first by mapping the major directories and public headers, then providing a practical guide for two audiences:
|
||||
- First‑time FastLED users who want to know which headers to include and how to use common features
|
||||
- Experienced C++ developers exploring subsystems and how the library is organized
|
||||
|
||||
The `src/` directory contains the classic FastLED public API (`FastLED.h`), the cross‑platform `fl::` foundation, effect/graphics utilities, platform backends, sensors, fonts/assets, and selected third‑party shims.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview and Quick Start](#overview-and-quick-start)
|
||||
- [What lives in `src/`](#what-lives-in-src)
|
||||
- [Include policy](#include-policy)
|
||||
- [Directory Map (7 major areas)](#directory-map-7-major-areas)
|
||||
- [Public headers and glue](#public-headers-and-glue)
|
||||
- [Core foundation: `fl/`](#core-foundation-fl)
|
||||
- [Effects and graphics: `fx/`](#effects-and-graphics-fx)
|
||||
- [Platforms and HAL: `platforms/`](#platforms-and-hal-platforms)
|
||||
- [Sensors and input: `sensors/`](#sensors-and-input-sensors)
|
||||
- [Fonts and assets: `fonts/`](#fonts-and-assets-fonts)
|
||||
- [Third‑party and shims: `third_party/`](#third-party-and-shims-third_party)
|
||||
- [Quick Usage Examples](#quick-usage-examples)
|
||||
- [Classic strip setup](#classic-strip-setup)
|
||||
- [2D matrix with `fl::Leds` + `XYMap`](#2d-matrix-with-flleds--xymap)
|
||||
- [Resampling pipeline (downscale/upscale)](#resampling-pipeline-downscaleupscale)
|
||||
- [JSON UI (WASM)](#json-ui-wasm)
|
||||
- [Deep Dives by Area](#deep-dives-by-area)
|
||||
- [Public API surface](#public-api-surface)
|
||||
- [Core foundation cross‑reference](#core-foundation-cross-reference)
|
||||
- [FX engine building blocks](#fx-engine-building-blocks)
|
||||
- [Platform layer and stubs](#platform-layer-and-stubs)
|
||||
- [WASM specifics](#wasm-specifics)
|
||||
- [Testing and compile‑time gates](#testing-and-compile-time-gates)
|
||||
- [Guidance for New Users](#guidance-for-new-users)
|
||||
- [Guidance for C++ Developers](#guidance-for-c-developers)
|
||||
|
||||
---
|
||||
|
||||
## Overview and Quick Start
|
||||
|
||||
### What lives in `src/`
|
||||
|
||||
- `FastLED.h` and classic public headers for sketches
|
||||
- `fl/`: cross‑platform foundation (containers, math, graphics primitives, async, JSON)
|
||||
- `fx/`: effect/graphics utilities and 1D/2D composition helpers
|
||||
- `platforms/`: hardware abstraction layers (AVR, ARM, ESP, POSIX, STUB, WASM, etc.)
|
||||
- `sensors/`: basic input components (buttons, digital pins)
|
||||
- `fonts/`: built‑in bitmap fonts for overlays
|
||||
- `third_party/`: vendored minimal headers and compatibility glue
|
||||
|
||||
If you are writing Arduino‑style sketches, include `FastLED.h`. For advanced/host builds or portable C++, prefer including only what you use from `fl/` and related subsystems.
|
||||
|
||||
### Include policy
|
||||
|
||||
- Sketches: `#include <FastLED.h>` for the canonical API
|
||||
- Advanced C++ (host, tests, platforms): include targeted headers (e.g., `"fl/vector.h"`, `"fl/downscale.h"`, `"fx/frame.h"`)
|
||||
- Prefer `fl::` facilities to keep portability across compilers and embedded targets. See `src/fl/README.md` for the full `fl::` guide.
|
||||
|
||||
---
|
||||
|
||||
## Directory Map (7 major areas)
|
||||
|
||||
### Public headers and glue
|
||||
|
||||
- Entry points: `FastLED.h`, color types, core utilities, and historical compatibility headers
|
||||
- Glue to bridge classic APIs to modern subsystems (e.g., `fl::` drawing/mapping)
|
||||
|
||||
### Core foundation: `fl/`
|
||||
|
||||
- Embedded‑aware STL‑like utilities, geometry/graphics primitives, color/math, async, JSON, I/O
|
||||
- Start here for containers, views, memory, rasterization, XY mapping, resampling, and UI glue
|
||||
- See `src/fl/README.md` for a comprehensive breakdown
|
||||
|
||||
### Effects and graphics: `fx/`
|
||||
|
||||
- Composition engine pieces: frames, layers, blenders, interpolators, and specialized effect helpers
|
||||
- 1D/2D utilities and video helpers for higher‑level effect construction
|
||||
|
||||
### Platforms and HAL: `platforms/`
|
||||
|
||||
- Target backends and shims for AVR, ARM, ESP, POSIX, STUB (for tests), WASM, etc.
|
||||
- Shared code for JSON UI, timing, and platform integrations
|
||||
|
||||
### Sensors and input: `sensors/`
|
||||
|
||||
- Minimal input primitives (e.g., `button`, `digital_pin`) intended for demos and portable logic
|
||||
|
||||
### Fonts and assets: `fonts/`
|
||||
|
||||
- Small bitmap fonts (e.g., `console_font_4x6`) to draw text overlays in demos/tests
|
||||
|
||||
### Third‑party and shims: `third_party/`
|
||||
|
||||
- Small vendored components such as `arduinojson` headers and platform shims kept intentionally minimal
|
||||
|
||||
---
|
||||
|
||||
## Quick Usage Examples
|
||||
|
||||
The following examples show how to use common capabilities reachable from `src/`.
|
||||
|
||||
### Classic strip setup
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
|
||||
constexpr uint16_t NUM_LEDS = 144;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
FastLED.setBrightness(160);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
fill_rainbow(leds, NUM_LEDS);
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
### 2D matrix with `fl::Leds` + `XYMap`
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/leds.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
constexpr uint16_t WIDTH = 16;
|
||||
constexpr uint16_t HEIGHT = 16;
|
||||
constexpr uint16_t NUM_LEDS = WIDTH * HEIGHT;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
auto map = fl::XYMap::constructSerpentine(WIDTH, HEIGHT);
|
||||
fl::Leds s = fl::Leds(leds, map);
|
||||
|
||||
for (uint16_t y = 0; y < HEIGHT; ++y) {
|
||||
for (uint16_t x = 0; x < WIDTH; ++x) {
|
||||
uint8_t hue = uint8_t((x * 255u) / (WIDTH ? WIDTH : 1));
|
||||
s(x, y) = CHSV(hue, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
### Resampling pipeline (downscale/upscale)
|
||||
|
||||
```cpp
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/grid.h"
|
||||
|
||||
// High‑def buffer drawn elsewhere
|
||||
fl::Grid<CRGB> srcHD(64, 64);
|
||||
|
||||
// Downscale into your physical panel resolution
|
||||
fl::Grid<CRGB> panel(16, 16);
|
||||
|
||||
void render_frame() {
|
||||
fl::downscale(srcHD, srcHD.size_xy(), panel, panel.size_xy());
|
||||
}
|
||||
```
|
||||
|
||||
### JSON UI (WASM)
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/ui.h"
|
||||
|
||||
UISlider brightness("Brightness", 128, 0, 255);
|
||||
UICheckbox enabled("Enabled", true);
|
||||
|
||||
void setup() {
|
||||
brightness.onChanged([](UISlider& s){ FastLED.setBrightness((uint8_t)s); });
|
||||
}
|
||||
|
||||
void loop() {
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
See `src/fl/README.md` and `platforms/shared/ui/json/readme.md` for JSON UI lifecycle and platform bridges.
|
||||
|
||||
---
|
||||
|
||||
## Deep Dives by Area
|
||||
|
||||
### Public API surface
|
||||
|
||||
- Sketch entry point: `FastLED.h`
|
||||
- Classic helpers: color conversions, dithering, palettes, HSV/CRGB utilities
|
||||
- Bridge to newer subsystems (e.g., treat `CRGB*` as an `fl::Leds` surface for 2D rendering)
|
||||
|
||||
### Core foundation cross‑reference
|
||||
|
||||
- The `fl::` namespace offers:
|
||||
- Containers and views (`vector`, `span`, `slice`)
|
||||
- Graphics/rasterization (`raster`, `xymap`, `rectangular_draw_buffer`)
|
||||
- Resampling (`downscale`, `upscale`, `supersample`)
|
||||
- Color/math (`hsv`, `hsv16`, `gamma`, `gradient`, `sin32`, `random`)
|
||||
- Async/functional (`task`, `promise`, `function_list`)
|
||||
- JSON and I/O (`json`, `ostream`, `printf`)
|
||||
- Detailed documentation: `src/fl/README.md`
|
||||
|
||||
### FX engine building blocks
|
||||
|
||||
- `fx/` contains an effect pipeline with frames, layers, and video helpers
|
||||
- Typical usage: assemble layers, blend or interpolate frames, and map output to LEDs via `fl::Leds`
|
||||
- Headers to look for: `fx/frame.h`, `fx/detail/*`, `fx/video/*` (exact APIs evolve; see headers)
|
||||
|
||||
### Platform layer and stubs
|
||||
|
||||
- `platforms/stub/`: host/test builds without hardware; ideal for CI and examples
|
||||
- `platforms/wasm/`: web builds with JSON UI, Asyncify integration, and JS glue
|
||||
- `platforms/arm/`, `platforms/esp/`, `platforms/avr/`, `platforms/posix/`: target‑specific shims
|
||||
- Compile‑time gates and helpers: see `platforms/*/*.h` and `platforms/ui_defs.h`
|
||||
|
||||
### WASM specifics
|
||||
|
||||
- JSON UI is enabled by default on WASM (`FASTLED_USE_JSON_UI=1`)
|
||||
- Browser UI is driven via platform glue (`src/platforms/wasm/ui.cpp`, JS modules)
|
||||
- Async patterns rely on the `fl::task`/engine events integration; see examples in `examples/` under `wasm/` topics
|
||||
|
||||
### Testing and compile‑time gates
|
||||
|
||||
- The source tree includes a rich test suite under `tests/` and CI utilities under `ci/`
|
||||
- For portability, prefer `fl::` primitives and compile‑time guards (`compiler_control.h`, `warn.h`, `assert.h`)
|
||||
- Many platform backends provide “fake” or stubbed facilities for host validation
|
||||
|
||||
---
|
||||
|
||||
## Guidance for New Users
|
||||
|
||||
- If you are writing sketches: include `FastLED.h` and follow examples in `examples/`
|
||||
- For 2D matrices or layouts: wrap your `CRGB*` with `fl::Leds` and an `fl::XYMap`
|
||||
- For high‑quality rendering: consider `supersample` + `downscale` to preserve detail
|
||||
- On WASM: explore the JSON UI to add sliders/buttons without platform‑specific code
|
||||
|
||||
## Guidance for C++ Developers
|
||||
|
||||
- Treat `fl::` as an embedded‑friendly STL and graphics/math foundation; prefer `fl::span<T>` for parameters
|
||||
- Use compiler‑portable control macros from `fl/` headers rather than raw pragmas
|
||||
- Keep platform dependencies behind `platforms/` shims and use the STUB backend for host tests
|
||||
- Prefer `fl::memfill` over `memset` where applicable to match codebase idioms
|
||||
|
||||
---
|
||||
|
||||
This README will evolve alongside the codebase. For subsystem details, jump into the directories listed above and consult header comments and `src/fl/README.md` for the foundation layer.
|
||||
21
libraries/FastLED/src/_readme
Normal file
21
libraries/FastLED/src/_readme
Normal file
@@ -0,0 +1,21 @@
|
||||
This is the root folder of FastLED.
|
||||
|
||||
Because of the way the Arduino IDE works, every header file put into this
|
||||
root folder will become available to all other libraries.
|
||||
|
||||
This created horrible problems with header name collisions. Therefore, we mandate that
|
||||
all new headers and cpp files that would have gone into this root folder, must now go
|
||||
into the fl/ folder. All new classes, function etc must be placed into the fl namespace.
|
||||
|
||||
There will be a longer term migration to move all root-wise fastled code into the fl namespace with operations to bring those names into the global space, selectively.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
**future example of how CRGB should be structured:**
|
||||
```
|
||||
// crgb.h -> fl/crgb.h
|
||||
//
|
||||
#include "fl/crgb.h"
|
||||
using fl::CRGB;
|
||||
```
|
||||
5
libraries/FastLED/src/bitswap.cpp
Normal file
5
libraries/FastLED/src/bitswap.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
/// @file bitswap.cpp
|
||||
/// Functions for doing a rotation of bits/bytes used by parallel output
|
||||
|
||||
/// Disables pragma messages and warnings
|
||||
#define FASTLED_INTERNAL
|
||||
296
libraries/FastLED/src/bitswap.h
Normal file
296
libraries/FastLED/src/bitswap.h
Normal file
@@ -0,0 +1,296 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __INC_BITSWAP_H
|
||||
#define __INC_BITSWAP_H
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
/// @file bitswap.h
|
||||
/// Functions for doing a rotation of bits/bytes used by parallel output
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
#if defined(FASTLED_ARM) || defined(FASTLED_ESP8266) || defined(FASTLED_DOXYGEN)
|
||||
/// Structure representing 8 bits of access
|
||||
typedef union {
|
||||
fl::u8 raw; ///< the entire byte
|
||||
struct {
|
||||
fl::u32 a0:1; ///< bit 0 (0x01)
|
||||
fl::u32 a1:1; ///< bit 1 (0x02)
|
||||
fl::u32 a2:1; ///< bit 2 (0x04)
|
||||
fl::u32 a3:1; ///< bit 3 (0x08)
|
||||
fl::u32 a4:1; ///< bit 4 (0x10)
|
||||
fl::u32 a5:1; ///< bit 5 (0x20)
|
||||
fl::u32 a6:1; ///< bit 6 (0x40)
|
||||
fl::u32 a7:1; ///< bit 7 (0x80)
|
||||
};
|
||||
} just8bits;
|
||||
|
||||
/// Structure representing 32 bits of access
|
||||
typedef struct {
|
||||
fl::u32 a0:1; ///< byte 'a', bit 0 (0x00000000)
|
||||
fl::u32 a1:1; ///< byte 'a', bit 1 (0x00000002)
|
||||
fl::u32 a2:1; ///< byte 'a', bit 2 (0x00000004)
|
||||
fl::u32 a3:1; ///< byte 'a', bit 3 (0x00000008)
|
||||
fl::u32 a4:1; ///< byte 'a', bit 4 (0x00000010)
|
||||
fl::u32 a5:1; ///< byte 'a', bit 5 (0x00000020)
|
||||
fl::u32 a6:1; ///< byte 'a', bit 6 (0x00000040)
|
||||
fl::u32 a7:1; ///< byte 'a', bit 7 (0x00000080)
|
||||
fl::u32 b0:1; ///< byte 'b', bit 0 (0x00000100)
|
||||
fl::u32 b1:1; ///< byte 'b', bit 1 (0x00000200)
|
||||
fl::u32 b2:1; ///< byte 'b', bit 2 (0x00000400)
|
||||
fl::u32 b3:1; ///< byte 'b', bit 3 (0x00000800)
|
||||
fl::u32 b4:1; ///< byte 'b', bit 4 (0x00001000)
|
||||
fl::u32 b5:1; ///< byte 'b', bit 5 (0x00002000)
|
||||
fl::u32 b6:1; ///< byte 'b', bit 6 (0x00004000)
|
||||
fl::u32 b7:1; ///< byte 'b', bit 7 (0x00008000)
|
||||
fl::u32 c0:1; ///< byte 'c', bit 0 (0x00010000)
|
||||
fl::u32 c1:1; ///< byte 'c', bit 1 (0x00020000)
|
||||
fl::u32 c2:1; ///< byte 'c', bit 2 (0x00040000)
|
||||
fl::u32 c3:1; ///< byte 'c', bit 3 (0x00080000)
|
||||
fl::u32 c4:1; ///< byte 'c', bit 4 (0x00100000)
|
||||
fl::u32 c5:1; ///< byte 'c', bit 5 (0x00200000)
|
||||
fl::u32 c6:1; ///< byte 'c', bit 6 (0x00400000)
|
||||
fl::u32 c7:1; ///< byte 'c', bit 7 (0x00800000)
|
||||
fl::u32 d0:1; ///< byte 'd', bit 0 (0x01000000)
|
||||
fl::u32 d1:1; ///< byte 'd', bit 1 (0x02000000)
|
||||
fl::u32 d2:1; ///< byte 'd', bit 2 (0x04000000)
|
||||
fl::u32 d3:1; ///< byte 'd', bit 3 (0x08000000)
|
||||
fl::u32 d4:1; ///< byte 'd', bit 4 (0x10000000)
|
||||
fl::u32 d5:1; ///< byte 'd', bit 5 (0x20000000)
|
||||
fl::u32 d6:1; ///< byte 'd', bit 6 (0x40000000)
|
||||
fl::u32 d7:1; ///< byte 'd', bit 7 (0x80000000)
|
||||
} sub4;
|
||||
|
||||
/// Union containing a full 8 bytes to swap the bit orientation on
|
||||
typedef union {
|
||||
fl::u32 word[2]; ///< two 32-bit values to load for swapping
|
||||
fl::u8 bytes[8]; ///< eight 8-bit values to load for swapping
|
||||
struct {
|
||||
sub4 a; ///< 32-bit access struct for bit swapping, upper four bytes (word[0] or bytes[0-3])
|
||||
sub4 b; ///< 32-bit access struct for bit swapping, lower four bytes (word[1] or bytes[4-7])
|
||||
};
|
||||
} bitswap_type;
|
||||
|
||||
|
||||
/// Set `out.X` bits 0, 1, 2, and 3 to bit N
|
||||
/// of `in.a.a`, `in.a.b`, `in.a.b`, `in.a.c`, and `in.a.d`
|
||||
/// @param X the sub4 of `out` to set
|
||||
/// @param N the bit of each byte to retrieve
|
||||
/// @see bitswap_type
|
||||
#define SWAPSA(X,N) out. X ## 0 = in.a.a ## N; \
|
||||
out. X ## 1 = in.a.b ## N; \
|
||||
out. X ## 2 = in.a.c ## N; \
|
||||
out. X ## 3 = in.a.d ## N;
|
||||
|
||||
/// Set `out.X` bits 0, 1, 2, and 3 to bit N
|
||||
/// of `in.b.a`, `in.b.b`, `in.b.b`, `in.b.c`, and `in.b.d`
|
||||
/// @param X the sub4 of `out` to set
|
||||
/// @param N the bit of each byte to retrieve
|
||||
/// @see bitswap_type
|
||||
#define SWAPSB(X,N) out. X ## 0 = in.b.a ## N; \
|
||||
out. X ## 1 = in.b.b ## N; \
|
||||
out. X ## 2 = in.b.c ## N; \
|
||||
out. X ## 3 = in.b.d ## N;
|
||||
|
||||
/// Set `out.X` bits to bit N of both `in.a` and `in.b`
|
||||
/// in order
|
||||
/// @param X the sub4 of `out` to set
|
||||
/// @param N the bit of each byte to retrieve
|
||||
/// @see bitswap_type
|
||||
#define SWAPS(X,N) out. X ## 0 = in.a.a ## N; \
|
||||
out. X ## 1 = in.a.b ## N; \
|
||||
out. X ## 2 = in.a.c ## N; \
|
||||
out. X ## 3 = in.a.d ## N; \
|
||||
out. X ## 4 = in.b.a ## N; \
|
||||
out. X ## 5 = in.b.b ## N; \
|
||||
out. X ## 6 = in.b.c ## N; \
|
||||
out. X ## 7 = in.b.d ## N;
|
||||
|
||||
|
||||
/// Do an 8-byte by 8-bit rotation
|
||||
FASTLED_FORCE_INLINE void swapbits8(bitswap_type in, bitswap_type & out) {
|
||||
|
||||
// SWAPS(a.a,7);
|
||||
// SWAPS(a.b,6);
|
||||
// SWAPS(a.c,5);
|
||||
// SWAPS(a.d,4);
|
||||
// SWAPS(b.a,3);
|
||||
// SWAPS(b.b,2);
|
||||
// SWAPS(b.c,1);
|
||||
// SWAPS(b.d,0);
|
||||
|
||||
// SWAPSA(a.a,7);
|
||||
// SWAPSA(a.b,6);
|
||||
// SWAPSA(a.c,5);
|
||||
// SWAPSA(a.d,4);
|
||||
//
|
||||
// SWAPSB(a.a,7);
|
||||
// SWAPSB(a.b,6);
|
||||
// SWAPSB(a.c,5);
|
||||
// SWAPSB(a.d,4);
|
||||
//
|
||||
// SWAPSA(b.a,3);
|
||||
// SWAPSA(b.b,2);
|
||||
// SWAPSA(b.c,1);
|
||||
// SWAPSA(b.d,0);
|
||||
// //
|
||||
// SWAPSB(b.a,3);
|
||||
// SWAPSB(b.b,2);
|
||||
// SWAPSB(b.c,1);
|
||||
// SWAPSB(b.d,0);
|
||||
|
||||
for(int i = 0; i < 8; ++i) {
|
||||
just8bits work;
|
||||
work.a3 = in.word[0] >> 31;
|
||||
work.a2 = in.word[0] >> 23;
|
||||
work.a1 = in.word[0] >> 15;
|
||||
work.a0 = in.word[0] >> 7;
|
||||
in.word[0] <<= 1;
|
||||
work.a7 = in.word[1] >> 31;
|
||||
work.a6 = in.word[1] >> 23;
|
||||
work.a5 = in.word[1] >> 15;
|
||||
work.a4 = in.word[1] >> 7;
|
||||
in.word[1] <<= 1;
|
||||
out.bytes[i] = work.raw;
|
||||
}
|
||||
}
|
||||
|
||||
/// Slow version of the 8 byte by 8 bit rotation
|
||||
FASTLED_FORCE_INLINE void slowswap(unsigned char *A, unsigned char *B) {
|
||||
|
||||
for(int row = 0; row < 7; ++row) {
|
||||
fl::u8 x = A[row];
|
||||
|
||||
fl::u8 bit = (1<<row);
|
||||
unsigned char *p = B;
|
||||
for(fl::u32 mask = 1<<7 ; mask ; mask >>= 1) {
|
||||
if(x & mask) {
|
||||
*p++ |= bit;
|
||||
} else {
|
||||
*p++ &= ~bit;
|
||||
}
|
||||
}
|
||||
// B[7] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[6] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[5] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[4] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[3] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[2] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[1] |= (x & 0x01) << row; x >>= 1;
|
||||
// B[0] |= (x & 0x01) << row; x >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified form of bits rotating function.
|
||||
/// This rotates data into LSB for a faster write (the code using this data can happily walk the array backwards).
|
||||
/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt
|
||||
void transpose8x1_noinline(unsigned char *A, unsigned char *B);
|
||||
|
||||
/// @copydoc transpose8x1_noinline()
|
||||
FASTLED_FORCE_INLINE void transpose8x1(unsigned char *A, unsigned char *B) {
|
||||
fl::u32 x, y, t;
|
||||
|
||||
// Load the array and pack it into x and y.
|
||||
y = *(unsigned int*)(A);
|
||||
x = *(unsigned int*)(A+4);
|
||||
|
||||
// pre-transform x
|
||||
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
|
||||
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
|
||||
|
||||
// pre-transform y
|
||||
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
|
||||
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
|
||||
|
||||
// final transform
|
||||
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
|
||||
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
|
||||
x = t;
|
||||
|
||||
*((uint32_t*)B) = y;
|
||||
*((uint32_t*)(B+4)) = x;
|
||||
}
|
||||
|
||||
/// Simplified form of bits rotating function.
|
||||
/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt
|
||||
FASTLED_FORCE_INLINE void transpose8x1_MSB(unsigned char *A, unsigned char *B) {
|
||||
fl::u32 x, y, t;
|
||||
|
||||
// Load the array and pack it into x and y.
|
||||
y = *(unsigned int*)(A);
|
||||
x = *(unsigned int*)(A+4);
|
||||
|
||||
// pre-transform x
|
||||
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
|
||||
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
|
||||
|
||||
// pre-transform y
|
||||
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
|
||||
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
|
||||
|
||||
// final transform
|
||||
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
|
||||
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
|
||||
x = t;
|
||||
|
||||
B[7] = y; y >>= 8;
|
||||
B[6] = y; y >>= 8;
|
||||
B[5] = y; y >>= 8;
|
||||
B[4] = y;
|
||||
|
||||
B[3] = x; x >>= 8;
|
||||
B[2] = x; x >>= 8;
|
||||
B[1] = x; x >>= 8;
|
||||
B[0] = x; /* */
|
||||
}
|
||||
|
||||
/// Templated bit-rotating function.
|
||||
/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt
|
||||
template<int m, int n>
|
||||
FASTLED_FORCE_INLINE void transpose8(unsigned char *A, unsigned char *B) {
|
||||
fl::u32 x, y, t;
|
||||
|
||||
// Load the array and pack it into x and y.
|
||||
if(m == 1) {
|
||||
y = *(unsigned int*)(A);
|
||||
x = *(unsigned int*)(A+4);
|
||||
} else {
|
||||
x = (A[0]<<24) | (A[m]<<16) | (A[2*m]<<8) | A[3*m];
|
||||
y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m];
|
||||
}
|
||||
|
||||
// pre-transform x
|
||||
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
|
||||
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
|
||||
|
||||
// pre-transform y
|
||||
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
|
||||
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
|
||||
|
||||
// final transform
|
||||
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
|
||||
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
|
||||
x = t;
|
||||
|
||||
B[7*n] = y; y >>= 8;
|
||||
B[6*n] = y; y >>= 8;
|
||||
B[5*n] = y; y >>= 8;
|
||||
B[4*n] = y;
|
||||
|
||||
B[3*n] = x; x >>= 8;
|
||||
B[2*n] = x; x >>= 8;
|
||||
B[n] = x; x >>= 8;
|
||||
B[0] = x;
|
||||
// B[0]=x>>24; B[n]=x>>16; B[2*n]=x>>8; B[3*n]=x>>0;
|
||||
// B[4*n]=y>>24; B[5*n]=y>>16; B[6*n]=y>>8; B[7*n]=y>>0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
1241
libraries/FastLED/src/chipsets.h
Normal file
1241
libraries/FastLED/src/chipsets.h
Normal file
File diff suppressed because it is too large
Load Diff
23
libraries/FastLED/src/chsv.h
Normal file
23
libraries/FastLED/src/chsv.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/// @file chsv.h
|
||||
/// Defines the hue, saturation, and value (HSV) pixel struct
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Legacy header. Prefer to use fl/hsv.h instead.
|
||||
*/
|
||||
#include "fl/hsv.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
|
||||
|
||||
using fl::CHSV;
|
||||
using fl::HSVHue;
|
||||
using fl::HUE_RED;
|
||||
using fl::HUE_ORANGE;
|
||||
using fl::HUE_YELLOW;
|
||||
using fl::HUE_GREEN;
|
||||
using fl::HUE_AQUA;
|
||||
using fl::HUE_BLUE;
|
||||
using fl::HUE_PURPLE;
|
||||
using fl::HUE_PINK;
|
||||
47
libraries/FastLED/src/cled_controller.cpp
Normal file
47
libraries/FastLED/src/cled_controller.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/// @file cled_controller.cpp
|
||||
/// base definitions used by led controllers for writing out led data
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "cled_controller.h"
|
||||
|
||||
#include "fl/memfill.h"
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
CLEDController::~CLEDController() = default;
|
||||
|
||||
/// Create an led controller object, add it to the chain of controllers
|
||||
CLEDController::CLEDController() : m_Data(NULL), m_ColorCorrection(UncorrectedColor), m_ColorTemperature(UncorrectedTemperature), m_DitherMode(BINARY_DITHER), m_nLeds(0) {
|
||||
m_pNext = NULL;
|
||||
if(m_pHead==NULL) { m_pHead = this; }
|
||||
if(m_pTail != NULL) { m_pTail->m_pNext = this; }
|
||||
m_pTail = this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CLEDController::clearLedDataInternal(int nLeds) {
|
||||
if(m_Data) {
|
||||
nLeds = (nLeds < 0) ? m_nLeds : nLeds;
|
||||
nLeds = (nLeds > m_nLeds) ? m_nLeds : nLeds;
|
||||
fl::memfill((void*)m_Data, 0, sizeof(struct CRGB) * nLeds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColorAdjustment CLEDController::getAdjustmentData(uint8_t brightness) {
|
||||
// *premixed = getAdjustment(brightness);
|
||||
// if (color_correction) {
|
||||
// *color_correction = getAdjustment(255);
|
||||
// }
|
||||
#if FASTLED_HD_COLOR_MIXING
|
||||
ColorAdjustment out = {this->getAdjustment(brightness), this->getAdjustment(255), brightness};
|
||||
#else
|
||||
ColorAdjustment out = {getAdjustment(brightness)};
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
285
libraries/FastLED/src/cled_controller.h
Normal file
285
libraries/FastLED/src/cled_controller.h
Normal file
@@ -0,0 +1,285 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/// @file cled_controller.h
|
||||
/// base definitions used by led controllers for writing out led data
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "led_sysdefs.h"
|
||||
#include "pixeltypes.h"
|
||||
#include "color.h"
|
||||
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/unused.h"
|
||||
#include "pixel_controller.h"
|
||||
#include "dither_mode.h"
|
||||
#include "pixel_iterator.h"
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/virtual_if_not_avr.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/bit_cast.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// LED Controller interface definition
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Base definition for an LED controller. Pretty much the methods that every LED controller object will make available.
|
||||
/// If you want to pass LED controllers around to methods, make them references to this type, keeps your code saner. However,
|
||||
/// most people won't be seeing/using these objects directly at all.
|
||||
/// @note That the methods for eventual checking of background writing of data (I'm looking at you, Teensy 3.0 DMA controller!)
|
||||
/// are not yet implemented.
|
||||
class CLEDController {
|
||||
protected:
|
||||
friend class CFastLED;
|
||||
CRGB *m_Data; ///< pointer to the LED data used by this controller
|
||||
CLEDController *m_pNext; ///< pointer to the next LED controller in the linked list
|
||||
CRGB m_ColorCorrection; ///< CRGB object representing the color correction to apply to the strip on show() @see setCorrection
|
||||
CRGB m_ColorTemperature; ///< CRGB object representing the color temperature to apply to the strip on show() @see setTemperature
|
||||
EDitherMode m_DitherMode; ///< the current dither mode of the controller
|
||||
bool m_enabled = true;
|
||||
int m_nLeds; ///< the number of LEDs in the LED data array
|
||||
static CLEDController *m_pHead; ///< pointer to the first LED controller in the linked list
|
||||
static CLEDController *m_pTail; ///< pointer to the last LED controller in the linked list
|
||||
|
||||
public:
|
||||
|
||||
/// Set all the LEDs to a given color.
|
||||
/// @param data the CRGB color to set the LEDs to
|
||||
/// @param nLeds the number of LEDs to set to this color
|
||||
/// @param scale the rgb scaling value for outputting color
|
||||
virtual void showColor(const CRGB & data, int nLeds, fl::u8 brightness) = 0;
|
||||
|
||||
/// Write the passed in RGB data out to the LEDs managed by this controller.
|
||||
/// @param data the rgb data to write out to the strip
|
||||
/// @param nLeds the number of LEDs being written out
|
||||
/// @param scale the rgb scaling to apply to each led before writing it out
|
||||
virtual void show(const struct CRGB *data, int nLeds, fl::u8 brightness) = 0;
|
||||
|
||||
|
||||
Rgbw mRgbMode = RgbwInvalid::value();
|
||||
CLEDController& setRgbw(const Rgbw& arg = RgbwDefault::value()) {
|
||||
// Note that at this time (Sept 13th, 2024) this is only implemented in the ESP32 driver
|
||||
// directly. For an emulated version please see RGBWEmulatedController in chipsets.h
|
||||
mRgbMode = arg;
|
||||
return *this; // builder pattern.
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled) { m_enabled = enabled; }
|
||||
bool getEnabled() { return m_enabled; }
|
||||
|
||||
CLEDController();
|
||||
// If we added virtual to the AVR boards then we are going to add 600 bytes of memory to the binary
|
||||
// flash size. This is because the virtual destructor pulls in malloc and free, which are the largest
|
||||
// Testing shows that this virtual destructor adds a 600 bytes to the binary on
|
||||
// attiny85 and about 1k for the teensy 4.X series.
|
||||
// Attiny85:
|
||||
// With CLEDController destructor virtual: 11018 bytes to binary.
|
||||
// Without CLEDController destructor virtual: 10666 bytes to binary.
|
||||
VIRTUAL_IF_NOT_AVR ~CLEDController();
|
||||
|
||||
Rgbw getRgbw() const { return mRgbMode; }
|
||||
|
||||
/// Initialize the LED controller
|
||||
virtual void init() = 0;
|
||||
|
||||
/// Clear out/zero out the given number of LEDs.
|
||||
/// @param nLeds the number of LEDs to clear
|
||||
VIRTUAL_IF_NOT_AVR void clearLeds(int nLeds = -1) {
|
||||
clearLedDataInternal(nLeds);
|
||||
showLeds(0);
|
||||
}
|
||||
|
||||
// Compatibility with the 3.8.x codebase.
|
||||
VIRTUAL_IF_NOT_AVR void showLeds(fl::u8 brightness) {
|
||||
void* data = beginShowLeds(m_nLeds);
|
||||
showLedsInternal(brightness);
|
||||
endShowLeds(data);
|
||||
}
|
||||
|
||||
ColorAdjustment getAdjustmentData(fl::u8 brightness);
|
||||
|
||||
/// @copybrief show(const struct CRGB*, int, CRGB)
|
||||
///
|
||||
/// Will scale for color correction and temperature. Can accept LED data not attached to this controller.
|
||||
/// @param data the LED data to write to the strip
|
||||
/// @param nLeds the number of LEDs in the data array
|
||||
/// @param brightness the brightness of the LEDs
|
||||
/// @see show(const struct CRGB*, int, CRGB)
|
||||
void showInternal(const struct CRGB *data, int nLeds, fl::u8 brightness) {
|
||||
if (m_enabled) {
|
||||
show(data, nLeds,brightness);
|
||||
}
|
||||
}
|
||||
|
||||
/// @copybrief showColor(const struct CRGB&, int, CRGB)
|
||||
///
|
||||
/// Will scale for color correction and temperature. Can accept LED data not attached to this controller.
|
||||
/// @param data the CRGB color to set the LEDs to
|
||||
/// @param nLeds the number of LEDs in the data array
|
||||
/// @param brightness the brightness of the LEDs
|
||||
/// @see showColor(const struct CRGB&, int, CRGB)
|
||||
void showColorInternal(const struct CRGB &data, int nLeds, fl::u8 brightness) {
|
||||
if (m_enabled) {
|
||||
showColor(data, nLeds, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the data to the LEDs managed by this controller
|
||||
/// @param brightness the brightness of the LEDs
|
||||
/// @see show(const struct CRGB*, int, fl::u8)
|
||||
void showLedsInternal(fl::u8 brightness) {
|
||||
if (m_enabled) {
|
||||
show(m_Data, m_nLeds, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
/// @copybrief showColor(const struct CRGB&, int, CRGB)
|
||||
///
|
||||
/// @param data the CRGB color to set the LEDs to
|
||||
/// @param brightness the brightness of the LEDs
|
||||
/// @see showColor(const struct CRGB&, int, CRGB)
|
||||
void showColorInternal(const struct CRGB & data, fl::u8 brightness) {
|
||||
if (m_enabled) {
|
||||
showColor(data, m_nLeds, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first LED controller in the linked list of controllers
|
||||
/// @returns CLEDController::m_pHead
|
||||
static CLEDController *head() { return m_pHead; }
|
||||
|
||||
/// Get the next controller in the linked list after this one. Will return NULL at the end of the linked list.
|
||||
/// @returns CLEDController::m_pNext
|
||||
CLEDController *next() { return m_pNext; }
|
||||
|
||||
/// Set the default array of LEDs to be used by this controller
|
||||
/// @param data pointer to the LED data
|
||||
/// @param nLeds the number of LEDs in the LED data
|
||||
CLEDController & setLeds(CRGB *data, int nLeds) {
|
||||
m_Data = data;
|
||||
m_nLeds = nLeds;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Zero out the LED data managed by this controller
|
||||
void clearLedDataInternal(int nLeds = -1);
|
||||
|
||||
/// How many LEDs does this controller manage?
|
||||
/// @returns CLEDController::m_nLeds
|
||||
virtual int size() { return m_nLeds; }
|
||||
|
||||
/// How many Lanes does this controller manage?
|
||||
/// @returns 1 for a non-Parallel controller
|
||||
virtual int lanes() { return 1; }
|
||||
|
||||
/// Pointer to the CRGB array for this controller
|
||||
/// @returns CLEDController::m_Data
|
||||
CRGB* leds() { return m_Data; }
|
||||
|
||||
/// Reference to the n'th LED managed by the controller
|
||||
/// @param x the LED number to retrieve
|
||||
/// @returns reference to CLEDController::m_Data[x]
|
||||
CRGB &operator[](int x) { return m_Data[x]; }
|
||||
|
||||
/// Set the dithering mode for this controller to use
|
||||
/// @param ditherMode the dithering mode to set
|
||||
/// @returns a reference to the controller
|
||||
inline CLEDController & setDither(fl::u8 ditherMode = BINARY_DITHER) { m_DitherMode = ditherMode; return *this; }
|
||||
|
||||
CLEDController& setScreenMap(const fl::XYMap& map, float diameter = -1.f) {
|
||||
// EngineEvents::onCanvasUiSet(this, map);
|
||||
fl::ScreenMap screenmap = map.toScreenMap();
|
||||
if (diameter <= 0.0f) {
|
||||
// screen map was not set.
|
||||
if (map.getTotal() <= (64*64)) {
|
||||
screenmap.setDiameter(.1f); // Assume small matrix is being used.
|
||||
}
|
||||
}
|
||||
fl::EngineEvents::onCanvasUiSet(this, screenmap);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CLEDController& setScreenMap(const fl::ScreenMap& map) {
|
||||
fl::EngineEvents::onCanvasUiSet(this, map);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CLEDController& setScreenMap(fl::u16 width, fl::u16 height, float diameter = -1.f) {
|
||||
fl::XYMap xymap = fl::XYMap::constructRectangularGrid(width, height);
|
||||
return setScreenMap(xymap, diameter);
|
||||
}
|
||||
|
||||
/// Get the dithering option currently set for this controller
|
||||
/// @return the currently set dithering option (CLEDController::m_DitherMode)
|
||||
inline fl::u8 getDither() { return m_DitherMode; }
|
||||
|
||||
virtual void* beginShowLeds(int size) {
|
||||
FASTLED_UNUSED(size);
|
||||
// By default, emit an integer. This integer will, by default, be passed back.
|
||||
// If you override beginShowLeds() then
|
||||
// you should also override endShowLeds() to match the return state.
|
||||
//
|
||||
// For async led controllers this should be used as a sync point to block
|
||||
// the caller until the leds from the last draw frame have completed drawing.
|
||||
// for each controller:
|
||||
// beginShowLeds();
|
||||
// for each controller:
|
||||
// showLeds();
|
||||
// for each controller:
|
||||
// endShowLeds();
|
||||
uintptr_t d = getDither();
|
||||
void* out = fl::int_to_ptr<void>(d);
|
||||
return out;
|
||||
}
|
||||
|
||||
virtual void endShowLeds(void* data) {
|
||||
// By default recieves the integer that beginShowLeds() emitted.
|
||||
//For async controllers this should be used to signal the controller
|
||||
// to begin transmitting the current frame to the leds.
|
||||
uintptr_t d = fl::ptr_to_int(data);
|
||||
setDither(static_cast<fl::u8>(d));
|
||||
}
|
||||
|
||||
/// The color corrction to use for this controller, expressed as a CRGB object
|
||||
/// @param correction the color correction to set
|
||||
/// @returns a reference to the controller
|
||||
CLEDController & setCorrection(CRGB correction) { m_ColorCorrection = correction; return *this; }
|
||||
|
||||
/// @copydoc setCorrection()
|
||||
CLEDController & setCorrection(LEDColorCorrection correction) { m_ColorCorrection = correction; return *this; }
|
||||
|
||||
/// Get the correction value used by this controller
|
||||
/// @returns the current color correction (CLEDController::m_ColorCorrection)
|
||||
CRGB getCorrection() { return m_ColorCorrection; }
|
||||
|
||||
/// Set the color temperature, aka white point, for this controller
|
||||
/// @param temperature the color temperature to set
|
||||
/// @returns a reference to the controller
|
||||
CLEDController & setTemperature(CRGB temperature) { m_ColorTemperature = temperature; return *this; }
|
||||
|
||||
/// @copydoc setTemperature()
|
||||
CLEDController & setTemperature(ColorTemperature temperature) { m_ColorTemperature = temperature; return *this; }
|
||||
|
||||
/// Get the color temperature, aka white point, for this controller
|
||||
/// @returns the current color temperature (CLEDController::m_ColorTemperature)
|
||||
CRGB getTemperature() { return m_ColorTemperature; }
|
||||
|
||||
/// Get the combined brightness/color adjustment for this controller
|
||||
/// @param scale the brightness scale to get the correction for
|
||||
/// @returns a CRGB object representing the total adjustment, including color correction and color temperature
|
||||
CRGB getAdjustment(fl::u8 scale) {
|
||||
return CRGB::computeAdjustment(scale, m_ColorCorrection, m_ColorTemperature);
|
||||
}
|
||||
|
||||
/// Gets the maximum possible refresh rate of the strip
|
||||
/// @returns the maximum refresh rate, in frames per second (FPS)
|
||||
virtual fl::u16 getMaxRefreshRate() const { return 0; }
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
100
libraries/FastLED/src/color.h
Normal file
100
libraries/FastLED/src/color.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// @file color.h
|
||||
/// Contains definitions for color correction and temperature
|
||||
|
||||
/// @defgroup ColorEnums Color Correction/Temperature
|
||||
/// Definitions for color correction and light temperatures
|
||||
/// @{
|
||||
|
||||
/// @brief Color correction starting points
|
||||
typedef enum {
|
||||
/// Typical values for SMD5050 LEDs
|
||||
TypicalSMD5050=0xFFB0F0 /* 255, 176, 240 */,
|
||||
/// @copydoc TypicalSMD5050
|
||||
TypicalLEDStrip=0xFFB0F0 /* 255, 176, 240 */,
|
||||
|
||||
/// Typical values for 8 mm "pixels on a string".
|
||||
/// Also for many through-hole 'T' package LEDs.
|
||||
Typical8mmPixel=0xFFE08C /* 255, 224, 140 */,
|
||||
/// @copydoc Typical8mmPixel
|
||||
TypicalPixelString=0xFFE08C /* 255, 224, 140 */,
|
||||
|
||||
/// Uncorrected color (0xFFFFFF)
|
||||
UncorrectedColor=0xFFFFFF /* 255, 255, 255 */
|
||||
|
||||
} LEDColorCorrection;
|
||||
|
||||
|
||||
/// @brief Color temperature values
|
||||
/// @details These color values are separated into two groups: black body radiators
|
||||
/// and gaseous light sources.
|
||||
///
|
||||
/// Black body radiators emit a (relatively) continuous spectrum,
|
||||
/// and can be described as having a Kelvin 'temperature'. This includes things
|
||||
/// like candles, tungsten lightbulbs, and sunlight.
|
||||
///
|
||||
/// Gaseous light sources emit discrete spectral bands, and while we can
|
||||
/// approximate their aggregate hue with RGB values, they don't actually
|
||||
/// have a proper Kelvin temperature.
|
||||
///
|
||||
/// @see https://en.wikipedia.org/wiki/Color_temperature
|
||||
typedef enum {
|
||||
// Black Body Radiators
|
||||
// @{
|
||||
/// 1900 Kelvin
|
||||
Candle=0xFF9329 /* 1900 K, 255, 147, 41 */,
|
||||
/// 2600 Kelvin
|
||||
Tungsten40W=0xFFC58F /* 2600 K, 255, 197, 143 */,
|
||||
/// 2850 Kelvin
|
||||
Tungsten100W=0xFFD6AA /* 2850 K, 255, 214, 170 */,
|
||||
/// 3200 Kelvin
|
||||
Halogen=0xFFF1E0 /* 3200 K, 255, 241, 224 */,
|
||||
/// 5200 Kelvin
|
||||
CarbonArc=0xFFFAF4 /* 5200 K, 255, 250, 244 */,
|
||||
/// 5400 Kelvin
|
||||
HighNoonSun=0xFFFFFB /* 5400 K, 255, 255, 251 */,
|
||||
/// 6000 Kelvin
|
||||
DirectSunlight=0xFFFFFF /* 6000 K, 255, 255, 255 */,
|
||||
/// 7000 Kelvin
|
||||
OvercastSky=0xC9E2FF /* 7000 K, 201, 226, 255 */,
|
||||
/// 20000 Kelvin
|
||||
ClearBlueSky=0x409CFF /* 20000 K, 64, 156, 255 */,
|
||||
// @}
|
||||
|
||||
// Gaseous Light Sources
|
||||
// @{
|
||||
/// Warm (yellower) flourescent light bulbs
|
||||
WarmFluorescent=0xFFF4E5 /* 0 K, 255, 244, 229 */,
|
||||
/// Standard flourescent light bulbs
|
||||
StandardFluorescent=0xF4FFFA /* 0 K, 244, 255, 250 */,
|
||||
/// Cool white (bluer) flourescent light bulbs
|
||||
CoolWhiteFluorescent=0xD4EBFF /* 0 K, 212, 235, 255 */,
|
||||
/// Full spectrum flourescent light bulbs
|
||||
FullSpectrumFluorescent=0xFFF4F2 /* 0 K, 255, 244, 242 */,
|
||||
/// Grow light flourescent light bulbs
|
||||
GrowLightFluorescent=0xFFEFF7 /* 0 K, 255, 239, 247 */,
|
||||
/// Black light flourescent light bulbs
|
||||
BlackLightFluorescent=0xA700FF /* 0 K, 167, 0, 255 */,
|
||||
/// Mercury vapor light bulbs
|
||||
MercuryVapor=0xD8F7FF /* 0 K, 216, 247, 255 */,
|
||||
/// Sodium vapor light bulbs
|
||||
SodiumVapor=0xFFD1B2 /* 0 K, 255, 209, 178 */,
|
||||
/// Metal-halide light bulbs
|
||||
MetalHalide=0xF2FCFF /* 0 K, 242, 252, 255 */,
|
||||
/// High-pressure sodium light bulbs
|
||||
HighPressureSodium=0xFFB74C /* 0 K, 255, 183, 76 */,
|
||||
// @}
|
||||
|
||||
/// Uncorrected temperature (0xFFFFFF)
|
||||
UncorrectedTemperature=0xFFFFFF /* 255, 255, 255 */
|
||||
} ColorTemperature;
|
||||
|
||||
/// @} ColorEnums
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
190
libraries/FastLED/src/colorpalettes.cpp
Normal file
190
libraries/FastLED/src/colorpalettes.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
|
||||
/// Disables pragma messages and warnings
|
||||
#define FASTLED_INTERNAL
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "colorutils.h"
|
||||
#include "colorpalettes.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
/// @file colorpalettes.cpp
|
||||
/// Definitions for the predefined color palettes supplied by FastLED.
|
||||
/// @note The documentation is in the source file instead of the header
|
||||
/// because it allows Doxygen to automatically inline the values that
|
||||
/// make up each palette.
|
||||
|
||||
/// @addtogroup ColorPalettes
|
||||
/// @{
|
||||
|
||||
/// @defgroup PredefinedPalettes Predefined Color Palettes
|
||||
/// Stock color palettes, only included when used.
|
||||
/// These palettes are all declared as `PROGMEM`, meaning
|
||||
/// that they won't take up SRAM on AVR chips until used.
|
||||
/// Furthermore, the compiler won't even include these
|
||||
/// in your PROGMEM (flash) storage unless you specifically
|
||||
/// use each one, so you only "pay for" those you actually use.
|
||||
/// @{
|
||||
|
||||
/// Cloudy color palette
|
||||
extern const TProgmemRGBPalette16 CloudColors_p FL_PROGMEM =
|
||||
{
|
||||
CRGB::Blue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
|
||||
CRGB::Blue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::SkyBlue,
|
||||
CRGB::SkyBlue,
|
||||
|
||||
CRGB::LightBlue,
|
||||
CRGB::White,
|
||||
CRGB::LightBlue,
|
||||
CRGB::SkyBlue
|
||||
};
|
||||
|
||||
/// Lava color palette
|
||||
extern const TProgmemRGBPalette16 LavaColors_p FL_PROGMEM =
|
||||
{
|
||||
CRGB::Black,
|
||||
CRGB::Maroon,
|
||||
CRGB::Black,
|
||||
CRGB::Maroon,
|
||||
|
||||
CRGB::DarkRed,
|
||||
CRGB::DarkRed,
|
||||
CRGB::Maroon,
|
||||
CRGB::DarkRed,
|
||||
|
||||
CRGB::DarkRed,
|
||||
CRGB::DarkRed,
|
||||
CRGB::Red,
|
||||
CRGB::Orange,
|
||||
|
||||
CRGB::White,
|
||||
CRGB::Orange,
|
||||
CRGB::Red,
|
||||
CRGB::DarkRed
|
||||
};
|
||||
|
||||
|
||||
/// Ocean colors, blues and whites
|
||||
extern const TProgmemRGBPalette16 OceanColors_p FL_PROGMEM =
|
||||
{
|
||||
CRGB::MidnightBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::MidnightBlue,
|
||||
CRGB::Navy,
|
||||
|
||||
CRGB::DarkBlue,
|
||||
CRGB::MediumBlue,
|
||||
CRGB::SeaGreen,
|
||||
CRGB::Teal,
|
||||
|
||||
CRGB::CadetBlue,
|
||||
CRGB::Blue,
|
||||
CRGB::DarkCyan,
|
||||
CRGB::CornflowerBlue,
|
||||
|
||||
CRGB::Aquamarine,
|
||||
CRGB::SeaGreen,
|
||||
CRGB::Aqua,
|
||||
CRGB::LightSkyBlue
|
||||
};
|
||||
|
||||
/// Forest colors, greens
|
||||
extern const TProgmemRGBPalette16 ForestColors_p FL_PROGMEM =
|
||||
{
|
||||
CRGB::DarkGreen,
|
||||
CRGB::DarkGreen,
|
||||
CRGB::DarkOliveGreen,
|
||||
CRGB::DarkGreen,
|
||||
|
||||
CRGB::Green,
|
||||
CRGB::ForestGreen,
|
||||
CRGB::OliveDrab,
|
||||
CRGB::Green,
|
||||
|
||||
CRGB::SeaGreen,
|
||||
CRGB::MediumAquamarine,
|
||||
CRGB::LimeGreen,
|
||||
CRGB::YellowGreen,
|
||||
|
||||
CRGB::LightGreen,
|
||||
CRGB::LawnGreen,
|
||||
CRGB::MediumAquamarine,
|
||||
CRGB::ForestGreen
|
||||
};
|
||||
|
||||
/// HSV Rainbow
|
||||
extern const TProgmemRGBPalette16 RainbowColors_p FL_PROGMEM =
|
||||
{
|
||||
0xFF0000, 0xD52A00, 0xAB5500, 0xAB7F00,
|
||||
0xABAB00, 0x56D500, 0x00FF00, 0x00D52A,
|
||||
0x00AB55, 0x0056AA, 0x0000FF, 0x2A00D5,
|
||||
0x5500AB, 0x7F0081, 0xAB0055, 0xD5002B
|
||||
};
|
||||
|
||||
/// Alias of RainbowStripeColors_p
|
||||
#define RainbowStripesColors_p RainbowStripeColors_p
|
||||
|
||||
/// HSV Rainbow colors with alternatating stripes of black
|
||||
extern const TProgmemRGBPalette16 RainbowStripeColors_p FL_PROGMEM =
|
||||
{
|
||||
0xFF0000, 0x000000, 0xAB5500, 0x000000,
|
||||
0xABAB00, 0x000000, 0x00FF00, 0x000000,
|
||||
0x00AB55, 0x000000, 0x0000FF, 0x000000,
|
||||
0x5500AB, 0x000000, 0xAB0055, 0x000000
|
||||
};
|
||||
|
||||
/// HSV color ramp: blue, purple, pink, red, orange, yellow (and back).
|
||||
/// Basically, everything but the greens, which tend to make
|
||||
/// people's skin look unhealthy. This palette is good for
|
||||
/// lighting at a club or party, where it'll be shining on people.
|
||||
extern const TProgmemRGBPalette16 PartyColors_p FL_PROGMEM =
|
||||
{
|
||||
0x5500AB, 0x84007C, 0xB5004B, 0xE5001B,
|
||||
0xE81700, 0xB84700, 0xAB7700, 0xABAB00,
|
||||
0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E,
|
||||
0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9
|
||||
};
|
||||
|
||||
/// Approximate "black body radiation" palette, akin to
|
||||
/// the FastLED HeatColor() function.
|
||||
/// It's recommended that you use values 0-240 rather than
|
||||
/// the usual 0-255, as the last 15 colors will be
|
||||
/// "wrapping around" from the hot end to the cold end,
|
||||
/// which looks wrong.
|
||||
extern const TProgmemRGBPalette16 HeatColors_p FL_PROGMEM =
|
||||
{
|
||||
0x000000,
|
||||
0x330000, 0x660000, 0x990000, 0xCC0000, 0xFF0000,
|
||||
0xFF3300, 0xFF6600, 0xFF9900, 0xFFCC00, 0xFFFF00,
|
||||
0xFFFF33, 0xFFFF66, 0xFFFF99, 0xFFFFCC, 0xFFFFFF
|
||||
};
|
||||
|
||||
|
||||
/// Rainbow gradient. Provided for situations where you're going
|
||||
/// to use a number of other gradient palettes, AND you want a
|
||||
/// "standard" FastLED rainbow as well.
|
||||
DEFINE_GRADIENT_PALETTE( Rainbow_gp ) {
|
||||
0, 255, 0, 0, // Red
|
||||
32, 171, 85, 0, // Orange
|
||||
64, 171, 171, 0, // Yellow
|
||||
96, 0, 255, 0, // Green
|
||||
128, 0, 171, 85, // Aqua
|
||||
160, 0, 0, 255, // Blue
|
||||
192, 85, 0, 171, // Purple
|
||||
224, 171, 0, 85, // Pink
|
||||
255, 255, 0, 0};// and back to Red
|
||||
|
||||
/// @}
|
||||
/// @}
|
||||
39
libraries/FastLED/src/colorpalettes.h
Normal file
39
libraries/FastLED/src/colorpalettes.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __INC_COLORPALETTES_H
|
||||
#define __INC_COLORPALETTES_H
|
||||
|
||||
// #include "FastLED.h"
|
||||
#include "colorutils.h"
|
||||
#include "fastled_progmem.h"
|
||||
|
||||
/// @file colorpalettes.h
|
||||
/// Declarations for the predefined color palettes supplied by FastLED.
|
||||
|
||||
// Have Doxygen ignore these declarations
|
||||
/// @cond
|
||||
|
||||
// For historical reasons, TProgmemRGBPalette and others may be
|
||||
// defined in sketches. Therefore we treat these as special
|
||||
// and bind to the global namespace.
|
||||
extern const TProgmemRGBPalette16 CloudColors_p FL_PROGMEM;
|
||||
extern const TProgmemRGBPalette16 LavaColors_p FL_PROGMEM;
|
||||
extern const TProgmemRGBPalette16 OceanColors_p FL_PROGMEM;
|
||||
extern const TProgmemRGBPalette16 ForestColors_p FL_PROGMEM;
|
||||
|
||||
extern const TProgmemRGBPalette16 RainbowColors_p FL_PROGMEM;
|
||||
|
||||
/// Alias of RainbowStripeColors_p
|
||||
#define RainbowStripesColors_p RainbowStripeColors_p
|
||||
extern const TProgmemRGBPalette16 RainbowStripeColors_p FL_PROGMEM;
|
||||
|
||||
extern const TProgmemRGBPalette16 PartyColors_p FL_PROGMEM;
|
||||
|
||||
extern const TProgmemRGBPalette16 HeatColors_p FL_PROGMEM;
|
||||
|
||||
|
||||
DECLARE_GRADIENT_PALETTE( Rainbow_gp);
|
||||
|
||||
/// @endcond
|
||||
|
||||
#endif
|
||||
130
libraries/FastLED/src/colorutils.h
Normal file
130
libraries/FastLED/src/colorutils.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Legacy header. Prefer to use fl/colorutils.h instead since
|
||||
*/
|
||||
#include "fl/colorutils.h"
|
||||
|
||||
using fl::fadeLightBy;
|
||||
using fl::fade_video;
|
||||
using fl::fadeToBlackBy;
|
||||
using fl::nscale8_video;
|
||||
using fl::fade_raw;
|
||||
using fl::nscale8;
|
||||
using fl::fadeUsingColor;
|
||||
using fl::blend;
|
||||
using fl::CRGBPalette16;
|
||||
using fl::CRGBPalette32;
|
||||
using fl::CRGBPalette256;
|
||||
using fl::CHSVPalette16;
|
||||
using fl::CHSVPalette32;
|
||||
using fl::CHSVPalette256;
|
||||
// using fl::TProgmemRGBPalette16;
|
||||
// using fl::TProgmemHSVPalette16;
|
||||
using fl::HeatColor;
|
||||
using fl::TRGBGradientPaletteEntryUnion;
|
||||
using fl::TDynamicRGBGradientPalette_byte;
|
||||
using fl::TDynamicRGBGradientPalette_bytes;
|
||||
// using fl::TProgmemRGBGradientPalette_bytes;
|
||||
// using fl::TProgmemRGBGradientPalette_byte;
|
||||
// using fl::TProgmemRGBPalette16;
|
||||
// using fl::TProgmemRGBGradientPaletteRef;
|
||||
|
||||
using fl::UpscalePalette;
|
||||
|
||||
|
||||
using fl::TDynamicRGBGradientPaletteRef;
|
||||
using fl::TGradientDirectionCode;
|
||||
using fl::TBlendType;
|
||||
using fl::ColorFromPalette;
|
||||
using fl::ColorFromPaletteExtended;
|
||||
using fl::fill_palette;
|
||||
using fl::fill_gradient;
|
||||
using fl::fill_rainbow;
|
||||
using fl::fill_rainbow_circular;
|
||||
using fl::fill_solid;
|
||||
using fl::fill_palette_circular;
|
||||
using fl::map_data_into_colors_through_palette;
|
||||
using fl::nblendPaletteTowardPalette;
|
||||
using fl::napplyGamma_video;
|
||||
using fl::blurColumns;
|
||||
using fl::blurRows;
|
||||
using fl::blur1d;
|
||||
using fl::blur2d;
|
||||
using fl::nblend;
|
||||
using fl::applyGamma_video;
|
||||
|
||||
using fl::fill_gradient_RGB;
|
||||
using fl::fill_gradient_HSV;
|
||||
|
||||
// TGradientDirectionCode values.
|
||||
using fl::SHORTEST_HUES;
|
||||
using fl::LONGEST_HUES;
|
||||
using fl::FORWARD_HUES;
|
||||
using fl::BACKWARD_HUES;
|
||||
|
||||
// TBlendType values.
|
||||
using fl::NOBLEND;
|
||||
using fl::BLEND;
|
||||
using fl::LINEARBLEND; ///< Linear interpolation between palette entries, with wrap-around from end to the beginning again
|
||||
using fl::LINEARBLEND_NOWRAP;
|
||||
|
||||
|
||||
/// Defines a static RGB palette very compactly using a series
|
||||
/// of connected color gradients.
|
||||
///
|
||||
/// For example, if you want the first 3/4ths of the palette to be a slow
|
||||
/// gradient ramping from black to red, and then the remaining 1/4 of the
|
||||
/// palette to be a quicker ramp to white, you specify just three points: the
|
||||
/// starting black point (at index 0), the red midpoint (at index 192),
|
||||
/// and the final white point (at index 255). It looks like this:
|
||||
/// @code
|
||||
/// index: 0 192 255
|
||||
/// |----------r-r-r-rrrrrrrrRrRrRrRrRRRR-|-RRWRWWRWWW-|
|
||||
/// color: (0,0,0) (255,0,0) (255,255,255)
|
||||
/// @endcode
|
||||
///
|
||||
/// Here's how you'd define that gradient palette using this macro:
|
||||
/// @code{.cpp}
|
||||
/// DEFINE_GRADIENT_PALETTE( black_to_red_to_white_p ) {
|
||||
/// 0, 0, 0, 0, /* at index 0, black(0,0,0) */
|
||||
/// 192, 255, 0, 0, /* at index 192, red(255,0,0) */
|
||||
/// 255, 255, 255, 255 /* at index 255, white(255,255,255) */
|
||||
/// };
|
||||
/// @endcode
|
||||
///
|
||||
/// This format is designed for compact storage. The example palette here
|
||||
/// takes up just 12 bytes of PROGMEM (flash) storage, and zero bytes
|
||||
/// of SRAM when not currently in use.
|
||||
///
|
||||
/// To use one of these gradient palettes, simply assign it into a
|
||||
/// CRGBPalette16 or a CRGBPalette256, like this:
|
||||
/// @code{.cpp}
|
||||
/// CRGBPalette16 pal = black_to_red_to_white_p;
|
||||
/// @endcode
|
||||
///
|
||||
/// When the assignment is made, the gradients are expanded out into
|
||||
/// either 16 or 256 palette entries, depending on the kind of palette
|
||||
/// object they're assigned to.
|
||||
///
|
||||
/// @warning The last "index" position **MUST** be 255! Failure to end
|
||||
/// with index 255 will result in program hangs or crashes.
|
||||
/// @par
|
||||
/// @warning At this point, these gradient palette definitions **MUST**
|
||||
/// be stored in PROGMEM on AVR-based Arduinos. If you use the
|
||||
/// `DEFINE_GRADIENT_PALETTE` macro, this is taken of automatically.
|
||||
///
|
||||
/// TProgmemRGBGradientPalette_byte must remain in the global namespace.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
|
||||
#define DEFINE_GRADIENT_PALETTE(X) \
|
||||
FL_ALIGN_PROGMEM \
|
||||
extern const TProgmemRGBGradientPalette_byte X[] FL_PROGMEM =
|
||||
|
||||
/// Forward-declaration macro for DEFINE_GRADIENT_PALETTE(X)
|
||||
#define DECLARE_GRADIENT_PALETTE(X) \
|
||||
FL_ALIGN_PROGMEM \
|
||||
extern const TProgmemRGBGradientPalette_byte X[] FL_PROGMEM
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
11
libraries/FastLED/src/controller.h
Normal file
11
libraries/FastLED/src/controller.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __INC_CONTROLLER_H
|
||||
#define __INC_CONTROLLER_H
|
||||
|
||||
/// @file controller.h
|
||||
/// deprecated: base definitions used by led controllers for writing out led data
|
||||
|
||||
#include "cpixel_ledcontroller.h"
|
||||
|
||||
#endif
|
||||
72
libraries/FastLED/src/cpixel_ledcontroller.h
Normal file
72
libraries/FastLED/src/cpixel_ledcontroller.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
/// @file cpixel_ledcontroller.h
|
||||
/// defines the templated version of the CLEDController class
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "led_sysdefs.h"
|
||||
#include "pixeltypes.h"
|
||||
#include "color.h"
|
||||
#include "eorder.h"
|
||||
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/int.h"
|
||||
#include "pixel_controller.h"
|
||||
#include "cled_controller.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
|
||||
/// Template extension of the CLEDController class
|
||||
/// @tparam RGB_ORDER the rgb ordering for the LEDs (e.g. what order red, green, and blue data is written out in)
|
||||
/// @tparam LANES how many parallel lanes of output to write
|
||||
/// @tparam MASK bitmask for the output lanes
|
||||
template<EOrder RGB_ORDER, int LANES=1, fl::u32 MASK=0xFFFFFFFF> class CPixelLEDController : public CLEDController {
|
||||
protected:
|
||||
|
||||
|
||||
/// Set all the LEDs on the controller to a given color
|
||||
/// @param data the CRGB color to set the LEDs to
|
||||
/// @param nLeds the number of LEDs to set to this color
|
||||
/// @param scale_pre_mixed the RGB scaling of color adjustment + global brightness to apply to each LED (in RGB8 mode).
|
||||
virtual void showColor(const CRGB& data, int nLeds, fl::u8 brightness) override {
|
||||
// CRGB premixed, color_correction;
|
||||
// getAdjustmentData(brightness, &premixed, &color_correction);
|
||||
// ColorAdjustment color_adjustment = {premixed, color_correction, brightness};
|
||||
ColorAdjustment color_adjustment = getAdjustmentData(brightness);
|
||||
PixelController<RGB_ORDER, LANES, MASK> pixels(data, nLeds, color_adjustment, getDither());
|
||||
showPixels(pixels);
|
||||
}
|
||||
|
||||
/// Write the passed in RGB data out to the LEDs managed by this controller
|
||||
/// @param data the RGB data to write out to the strip
|
||||
/// @param nLeds the number of LEDs being written out
|
||||
/// @param scale_pre_mixed the RGB scaling of color adjustment + global brightness to apply to each LED (in RGB8 mode).
|
||||
virtual void show(const struct CRGB *data, int nLeds, fl::u8 brightness) override {
|
||||
ColorAdjustment color_adjustment = getAdjustmentData(brightness);
|
||||
PixelController<RGB_ORDER, LANES, MASK> pixels(data, nLeds < 0 ? -nLeds : nLeds, color_adjustment, getDither());
|
||||
if(nLeds < 0) {
|
||||
// nLeds < 0 implies that we want to show them in reverse
|
||||
pixels.mAdvance = -pixels.mAdvance;
|
||||
}
|
||||
showPixels(pixels);
|
||||
}
|
||||
|
||||
public:
|
||||
static const EOrder RGB_ORDER_VALUE = RGB_ORDER; ///< The RGB ordering for this controller
|
||||
static const int LANES_VALUE = LANES; ///< The number of lanes for this controller
|
||||
static const fl::u32 MASK_VALUE = MASK; ///< The mask for the lanes for this controller
|
||||
CPixelLEDController() : CLEDController() {}
|
||||
|
||||
/// Send the LED data to the strip
|
||||
/// @param pixels the PixelController object for the LED data
|
||||
virtual void showPixels(PixelController<RGB_ORDER,LANES,MASK> & pixels) = 0;
|
||||
|
||||
/// Get the number of lanes of the Controller
|
||||
/// @returns LANES from template
|
||||
int lanes() override { return LANES; }
|
||||
};
|
||||
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
28
libraries/FastLED/src/cpp_compat.h
Normal file
28
libraries/FastLED/src/cpp_compat.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
/// @file cpp_compat.h
|
||||
/// Compatibility functions based on C++ version
|
||||
|
||||
#ifndef __INC_CPP_COMPAT_H
|
||||
#define __INC_CPP_COMPAT_H
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#if __cplusplus <= 199711L
|
||||
|
||||
/// Compile-time assertion checking, introduced in C++11
|
||||
/// @see https://en.cppreference.com/w/cpp/language/static_assert
|
||||
#define static_assert(expression, message)
|
||||
|
||||
/// Declares that it is possible to evaluate a value at compile time, introduced in C++11
|
||||
/// @see https://en.cppreference.com/w/cpp/language/constexpr
|
||||
#define constexpr const
|
||||
|
||||
#else
|
||||
|
||||
// things that we can turn on if we're in a C++11 environment
|
||||
#endif
|
||||
|
||||
#include "fl/register.h"
|
||||
|
||||
#endif
|
||||
117
libraries/FastLED/src/crgb.cpp
Normal file
117
libraries/FastLED/src/crgb.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/// @file crgb.cpp
|
||||
/// Utility functions for the red, green, and blue (RGB) pixel struct
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "crgb.h"
|
||||
#include "FastLED.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/downscale.h"
|
||||
#include "lib8tion/math8.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
fl::string CRGB::toString() const {
|
||||
fl::string out;
|
||||
out.append("CRGB(");
|
||||
out.append(int16_t(r));
|
||||
out.append(",");
|
||||
out.append(int16_t(g));
|
||||
out.append(",");
|
||||
out.append(int16_t(b));
|
||||
out.append(")");
|
||||
return out;
|
||||
}
|
||||
|
||||
CRGB CRGB::computeAdjustment(uint8_t scale, const CRGB &colorCorrection,
|
||||
const CRGB &colorTemperature) {
|
||||
#if defined(NO_CORRECTION) && (NO_CORRECTION == 1)
|
||||
return CRGB(scale, scale, scale);
|
||||
#else
|
||||
CRGB adj(0, 0, 0);
|
||||
if (scale > 0) {
|
||||
for (uint8_t i = 0; i < 3; ++i) {
|
||||
uint8_t cc = colorCorrection.raw[i];
|
||||
uint8_t ct = colorTemperature.raw[i];
|
||||
if (cc > 0 && ct > 0) {
|
||||
// Optimized for AVR size. This function is only called very
|
||||
// infrequently so size matters more than speed.
|
||||
fl::u32 work = (((fl::u16)cc) + 1);
|
||||
work *= (((fl::u16)ct) + 1);
|
||||
work *= scale;
|
||||
work /= 0x10000L;
|
||||
adj.raw[i] = work & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
return adj;
|
||||
#endif
|
||||
}
|
||||
|
||||
CRGB CRGB::blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2) {
|
||||
return CRGB(blend8(p1.r, p2.r, amountOfP2), blend8(p1.g, p2.g, amountOfP2),
|
||||
blend8(p1.b, p2.b, amountOfP2));
|
||||
}
|
||||
|
||||
CRGB CRGB::blendAlphaMaxChannel(const CRGB &upper, const CRGB &lower) {
|
||||
// Use luma of upper pixel as alpha (0..255)
|
||||
uint8_t max_component = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (upper.raw[i] > max_component) {
|
||||
max_component = upper.raw[i];
|
||||
}
|
||||
}
|
||||
// uint8_t alpha = upper.getLuma();
|
||||
// blend(lower, upper, alpha) → (lower * (255−alpha) + upper * alpha) / 256
|
||||
uint8_t amountOf2 = 255 - max_component;
|
||||
return CRGB::blend(upper, lower, amountOf2);
|
||||
}
|
||||
|
||||
void CRGB::downscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst,
|
||||
const fl::XYMap &dstXY) {
|
||||
fl::downscale(src, srcXY, dst, dstXY);
|
||||
}
|
||||
|
||||
void CRGB::upscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst,
|
||||
const fl::XYMap &dstXY) {
|
||||
FASTLED_WARN_IF(
|
||||
srcXY.getType() != fl::XYMap::kLineByLine,
|
||||
"Upscaling only works with a src matrix that is rectangular");
|
||||
fl::u16 w = srcXY.getWidth();
|
||||
fl::u16 h = srcXY.getHeight();
|
||||
fl::upscale(src, dst, w, h, dstXY);
|
||||
}
|
||||
|
||||
CRGB &CRGB::nscale8(uint8_t scaledown) {
|
||||
nscale8x3(r, g, b, scaledown);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add one CRGB to another, saturating at 0xFF for each channel
|
||||
CRGB &CRGB::operator+=(const CRGB &rhs) {
|
||||
r = qadd8(r, rhs.r);
|
||||
g = qadd8(g, rhs.g);
|
||||
b = qadd8(b, rhs.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CRGB CRGB::lerp8(const CRGB &other, fract8 amountOf2) const {
|
||||
CRGB ret;
|
||||
|
||||
ret.r = lerp8by8(r, other.r, amountOf2);
|
||||
ret.g = lerp8by8(g, other.g, amountOf2);
|
||||
ret.b = lerp8by8(b, other.b, amountOf2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CRGB &CRGB::fadeToBlackBy(uint8_t fadefactor) {
|
||||
nscale8x3(r, g, b, 255 - fadefactor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
890
libraries/FastLED/src/crgb.h
Normal file
890
libraries/FastLED/src/crgb.h
Normal file
@@ -0,0 +1,890 @@
|
||||
/// @file crgb.h
|
||||
/// Defines the red, green, and blue (RGB) pixel struct
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "chsv.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "color.h"
|
||||
#include "lib8tion/types.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "hsv2rgb.h"
|
||||
#include "fl/ease.h"
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
class string;
|
||||
class XYMap;
|
||||
struct HSV16;
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
// Whether to allow HD_COLOR_MIXING
|
||||
#ifndef FASTLED_HD_COLOR_MIXING
|
||||
#ifdef __AVR__
|
||||
// Saves some memory on these constrained devices.
|
||||
#define FASTLED_HD_COLOR_MIXING 0
|
||||
#else
|
||||
#define FASTLED_HD_COLOR_MIXING 1
|
||||
#endif // __AVR__
|
||||
#endif // FASTLED_HD_COLOR_MIXING
|
||||
|
||||
|
||||
struct CRGB;
|
||||
|
||||
|
||||
|
||||
/// HSV conversion function selection based on compile-time defines
|
||||
/// This allows users to configure which HSV conversion algorithm to use
|
||||
/// by setting one of the following defines:
|
||||
/// - FASTLED_HSV_CONVERSION_SPECTRUM: Use spectrum conversion
|
||||
/// - FASTLED_HSV_CONVERSION_FULL_SPECTRUM: Use full spectrum conversion
|
||||
/// - FASTLED_HSV_CONVERSION_RAINBOW: Use rainbow conversion (explicit)
|
||||
/// - Default (no define): Use rainbow conversion (backward compatibility)
|
||||
|
||||
|
||||
|
||||
/// Array-based HSV conversion function selection using the same compile-time defines
|
||||
/// @param phsv CHSV array to convert to RGB
|
||||
/// @param prgb CRGB array to store the result of the conversion (will be modified)
|
||||
/// @param numLeds the number of array values to process
|
||||
FASTLED_FORCE_INLINE void hsv2rgb_dispatch( const struct CHSV* phsv, struct CRGB * prgb, int numLeds)
|
||||
{
|
||||
#if defined(FASTLED_HSV_CONVERSION_SPECTRUM)
|
||||
hsv2rgb_spectrum(phsv, prgb, numLeds);
|
||||
#elif defined(FASTLED_HSV_CONVERSION_FULL_SPECTRUM)
|
||||
hsv2rgb_fullspectrum(phsv, prgb, numLeds);
|
||||
#elif defined(FASTLED_HSV_CONVERSION_RAINBOW)
|
||||
hsv2rgb_rainbow(phsv, prgb, numLeds);
|
||||
#else
|
||||
// Default to rainbow for backward compatibility
|
||||
hsv2rgb_rainbow(phsv, prgb, numLeds);
|
||||
#endif
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE void hsv2rgb_dispatch( const struct CHSV& hsv, struct CRGB& rgb)
|
||||
{
|
||||
#if defined(FASTLED_HSV_CONVERSION_SPECTRUM)
|
||||
hsv2rgb_spectrum(hsv, rgb);
|
||||
#elif defined(FASTLED_HSV_CONVERSION_FULL_SPECTRUM)
|
||||
hsv2rgb_fullspectrum(hsv, rgb);
|
||||
#elif defined(FASTLED_HSV_CONVERSION_RAINBOW)
|
||||
hsv2rgb_rainbow(hsv, rgb);
|
||||
#else
|
||||
hsv2rgb_rainbow(hsv, rgb);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// Representation of an RGB pixel (Red, Green, Blue)
|
||||
struct CRGB {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
fl::u8 r; ///< Red channel value
|
||||
fl::u8 red; ///< @copydoc r
|
||||
};
|
||||
union {
|
||||
fl::u8 g; ///< Green channel value
|
||||
fl::u8 green; ///< @copydoc g
|
||||
};
|
||||
union {
|
||||
fl::u8 b; ///< Blue channel value
|
||||
fl::u8 blue; ///< @copydoc b
|
||||
};
|
||||
};
|
||||
/// Access the red, green, and blue data as an array.
|
||||
/// Where:
|
||||
/// * `raw[0]` is the red value
|
||||
/// * `raw[1]` is the green value
|
||||
/// * `raw[2]` is the blue value
|
||||
fl::u8 raw[3];
|
||||
};
|
||||
|
||||
static CRGB blend(const CRGB& p1, const CRGB& p2, fract8 amountOfP2);
|
||||
static CRGB blendAlphaMaxChannel(const CRGB& upper, const CRGB& lower);
|
||||
|
||||
/// Downscale an CRGB matrix (or strip) to the smaller size.
|
||||
static void downscale(const CRGB* src, const fl::XYMap& srcXY, CRGB* dst, const fl::XYMap& dstXY);
|
||||
static void upscale(const CRGB* src, const fl::XYMap& srcXY, CRGB* dst, const fl::XYMap& dstXY);
|
||||
|
||||
// Are you using WS2812 (or other RGB8 LEDS) to display video?
|
||||
// Does it look washed out and under-saturated?
|
||||
//
|
||||
// Have you tried gamma correction but hate how it decimates the color into 8 colors per component?
|
||||
//
|
||||
// This is an alternative to gamma correction that preserves the hue but boosts the saturation.
|
||||
//
|
||||
// decimating the color from 8 bit -> gamma -> 8 bit (leaving only 8 colors for each component).
|
||||
// work well with WS2812 (and other RGB8) led displays.
|
||||
// This function converts to HSV16, boosts the saturation, and converts back to RGB8.
|
||||
// Note that when both boost_saturation and boost_contrast are true the resulting
|
||||
// pixel will be nearly the same as if you had used gamma correction pow = 2.0.
|
||||
CRGB colorBoost(fl::EaseType saturation_function = fl::EASE_NONE, fl::EaseType luminance_function = fl::EASE_NONE) const;
|
||||
static void colorBoost(const CRGB* src, CRGB* dst, size_t count, fl::EaseType saturation_function = fl::EASE_NONE, fl::EaseType luminance_function = fl::EASE_NONE);
|
||||
|
||||
// Want to do advanced color manipulation in HSV and write back to CRGB?
|
||||
// You want to use HSV16, which is much better at preservering the color
|
||||
// space than CHSV.
|
||||
fl::HSV16 toHSV16() const;
|
||||
|
||||
/// Array access operator to index into the CRGB object
|
||||
/// @param x the index to retrieve (0-2)
|
||||
/// @returns the CRGB::raw value for the given index
|
||||
FASTLED_FORCE_INLINE fl::u8& operator[] (fl::u8 x)
|
||||
{
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
/// Array access operator to index into the CRGB object
|
||||
/// @param x the index to retrieve (0-2)
|
||||
/// @returns the CRGB::raw value for the given index
|
||||
FASTLED_FORCE_INLINE const fl::u8& operator[] (fl::u8 x) const
|
||||
{
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
#if defined(__AVR__)
|
||||
// Saves a surprising amount of memory on AVR devices.
|
||||
CRGB() = default;
|
||||
#else
|
||||
/// Default constructor
|
||||
FASTLED_FORCE_INLINE CRGB() {
|
||||
r = 0;
|
||||
g = 0;
|
||||
b = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Allow construction from red, green, and blue
|
||||
/// @param ir input red value
|
||||
/// @param ig input green value
|
||||
/// @param ib input blue value
|
||||
constexpr CRGB(fl::u8 ir, fl::u8 ig, fl::u8 ib) noexcept
|
||||
: r(ir), g(ig), b(ib)
|
||||
{
|
||||
}
|
||||
|
||||
/// Allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code
|
||||
/// @param colorcode a packed 24 bit color code
|
||||
constexpr CRGB(fl::u32 colorcode) noexcept
|
||||
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr fl::u32 as_uint32_t() const noexcept {
|
||||
return fl::u32(0xff000000) |
|
||||
(fl::u32{r} << 16) |
|
||||
(fl::u32{g} << 8) |
|
||||
fl::u32{b};
|
||||
}
|
||||
|
||||
/// Allow construction from a LEDColorCorrection enum
|
||||
/// @param colorcode an LEDColorCorrect enumeration value
|
||||
constexpr CRGB(LEDColorCorrection colorcode) noexcept
|
||||
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF)
|
||||
{
|
||||
}
|
||||
|
||||
/// Allow construction from a ColorTemperature enum
|
||||
/// @param colorcode an ColorTemperature enumeration value
|
||||
constexpr CRGB(ColorTemperature colorcode) noexcept
|
||||
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF)
|
||||
{
|
||||
}
|
||||
|
||||
/// Allow copy construction
|
||||
FASTLED_FORCE_INLINE CRGB(const CRGB& rhs) = default;
|
||||
|
||||
/// Allow construction from a CHSV color
|
||||
FASTLED_FORCE_INLINE CRGB(const CHSV& rhs)
|
||||
{
|
||||
hsv2rgb_dispatch( rhs, *this);
|
||||
}
|
||||
|
||||
/// Allow construction from a fl::HSV16 color
|
||||
/// Enables automatic conversion from HSV16 to CRGB
|
||||
CRGB(const fl::HSV16& rhs);
|
||||
|
||||
/// Allow assignment from one RGB struct to another
|
||||
FASTLED_FORCE_INLINE CRGB& operator= (const CRGB& rhs) = default;
|
||||
|
||||
/// Allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code
|
||||
/// @param colorcode a packed 24 bit color code
|
||||
FASTLED_FORCE_INLINE CRGB& operator= (const fl::u32 colorcode)
|
||||
{
|
||||
r = (colorcode >> 16) & 0xFF;
|
||||
g = (colorcode >> 8) & 0xFF;
|
||||
b = (colorcode >> 0) & 0xFF;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Allow assignment from red, green, and blue
|
||||
/// @param nr new red value
|
||||
/// @param ng new green value
|
||||
/// @param nb new blue value
|
||||
FASTLED_FORCE_INLINE CRGB& setRGB (fl::u8 nr, fl::u8 ng, fl::u8 nb)
|
||||
{
|
||||
r = nr;
|
||||
g = ng;
|
||||
b = nb;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Allow assignment from hue, saturation, and value
|
||||
/// @param hue color hue
|
||||
/// @param sat color saturation
|
||||
/// @param val color value (brightness)
|
||||
FASTLED_FORCE_INLINE CRGB& setHSV (fl::u8 hue, fl::u8 sat, fl::u8 val)
|
||||
{
|
||||
hsv2rgb_dispatch( CHSV(hue, sat, val), *this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Allow assignment from just a hue.
|
||||
/// Saturation and value (brightness) are set automatically to max.
|
||||
/// @param hue color hue
|
||||
FASTLED_FORCE_INLINE CRGB& setHue (fl::u8 hue)
|
||||
{
|
||||
hsv2rgb_dispatch( CHSV(hue, 255, 255), *this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Allow assignment from HSV color
|
||||
FASTLED_FORCE_INLINE CRGB& operator= (const CHSV& rhs)
|
||||
{
|
||||
hsv2rgb_dispatch( rhs, *this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code
|
||||
/// @param colorcode a packed 24 bit color code
|
||||
FASTLED_FORCE_INLINE CRGB& setColorCode (fl::u32 colorcode)
|
||||
{
|
||||
r = (colorcode >> 16) & 0xFF;
|
||||
g = (colorcode >> 8) & 0xFF;
|
||||
b = (colorcode >> 0) & 0xFF;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/// Add one CRGB to another, saturating at 0xFF for each channel
|
||||
CRGB& operator+= (const CRGB& rhs);
|
||||
|
||||
/// Add a constant to each channel, saturating at 0xFF.
|
||||
/// @note This is NOT an operator+= overload because the compiler
|
||||
/// can't usefully decide when it's being passed a 32-bit
|
||||
/// constant (e.g. CRGB::Red) and an 8-bit one (CRGB::Blue)
|
||||
FASTLED_FORCE_INLINE CRGB& addToRGB (fl::u8 d);
|
||||
|
||||
/// Subtract one CRGB from another, saturating at 0x00 for each channel
|
||||
FASTLED_FORCE_INLINE CRGB& operator-= (const CRGB& rhs);
|
||||
|
||||
/// Subtract a constant from each channel, saturating at 0x00.
|
||||
/// @note This is NOT an operator+= overload because the compiler
|
||||
/// can't usefully decide when it's being passed a 32-bit
|
||||
/// constant (e.g. CRGB::Red) and an 8-bit one (CRGB::Blue)
|
||||
FASTLED_FORCE_INLINE CRGB& subtractFromRGB(fl::u8 d);
|
||||
|
||||
/// Subtract a constant of '1' from each channel, saturating at 0x00
|
||||
FASTLED_FORCE_INLINE CRGB& operator-- ();
|
||||
|
||||
/// @copydoc operator--
|
||||
FASTLED_FORCE_INLINE CRGB operator-- (int );
|
||||
|
||||
/// Add a constant of '1' from each channel, saturating at 0xFF
|
||||
FASTLED_FORCE_INLINE CRGB& operator++ ();
|
||||
|
||||
/// @copydoc operator++
|
||||
FASTLED_FORCE_INLINE CRGB operator++ (int );
|
||||
|
||||
/// Divide each of the channels by a constant
|
||||
FASTLED_FORCE_INLINE CRGB& operator/= (fl::u8 d )
|
||||
{
|
||||
r /= d;
|
||||
g /= d;
|
||||
b /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Right shift each of the channels by a constant
|
||||
FASTLED_FORCE_INLINE CRGB& operator>>= (fl::u8 d)
|
||||
{
|
||||
r >>= d;
|
||||
g >>= d;
|
||||
b >>= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Multiply each of the channels by a constant,
|
||||
/// saturating each channel at 0xFF.
|
||||
FASTLED_FORCE_INLINE CRGB& operator*= (fl::u8 d);
|
||||
|
||||
/// Scale down a RGB to N/256ths of it's current brightness using
|
||||
/// "video" dimming rules. "Video" dimming rules means that unless the scale factor
|
||||
/// is ZERO each channel is guaranteed NOT to dim down to zero. If it's already
|
||||
/// nonzero, it'll stay nonzero, even if that means the hue shifts a little
|
||||
/// at low brightness levels.
|
||||
/// @see nscale8x3_video
|
||||
FASTLED_FORCE_INLINE CRGB& nscale8_video (fl::u8 scaledown);
|
||||
|
||||
/// %= is a synonym for nscale8_video(). Think of it is scaling down
|
||||
/// by "a percentage"
|
||||
FASTLED_FORCE_INLINE CRGB& operator%= (fl::u8 scaledown);
|
||||
|
||||
/// fadeLightBy is a synonym for nscale8_video(), as a fade instead of a scale
|
||||
/// @param fadefactor the amount to fade, sent to nscale8_video() as (255 - fadefactor)
|
||||
FASTLED_FORCE_INLINE CRGB& fadeLightBy (fl::u8 fadefactor );
|
||||
|
||||
/// Scale down a RGB to N/256ths of its current brightness, using
|
||||
/// "plain math" dimming rules. "Plain math" dimming rules means that the low light
|
||||
/// levels may dim all the way to 100% black.
|
||||
/// @see nscale8x3
|
||||
CRGB& nscale8 (fl::u8 scaledown );
|
||||
|
||||
/// Scale down a RGB to N/256ths of its current brightness, using
|
||||
/// "plain math" dimming rules. "Plain math" dimming rules means that the low light
|
||||
/// levels may dim all the way to 100% black.
|
||||
/// @see ::scale8
|
||||
FASTLED_FORCE_INLINE CRGB& nscale8 (const CRGB & scaledown );
|
||||
|
||||
constexpr CRGB nscale8_constexpr (const CRGB scaledown ) const;
|
||||
|
||||
|
||||
/// Return a CRGB object that is a scaled down version of this object
|
||||
FASTLED_FORCE_INLINE CRGB scale8 (fl::u8 scaledown ) const;
|
||||
|
||||
/// Return a CRGB object that is a scaled down version of this object
|
||||
FASTLED_FORCE_INLINE CRGB scale8 (const CRGB & scaledown ) const;
|
||||
|
||||
/// fadeToBlackBy is a synonym for nscale8(), as a fade instead of a scale
|
||||
/// @param fadefactor the amount to fade, sent to nscale8() as (255 - fadefactor)
|
||||
CRGB& fadeToBlackBy (fl::u8 fadefactor );
|
||||
|
||||
/// "or" operator brings each channel up to the higher of the two values
|
||||
FASTLED_FORCE_INLINE CRGB& operator|= (const CRGB& rhs )
|
||||
{
|
||||
if( rhs.r > r) r = rhs.r;
|
||||
if( rhs.g > g) g = rhs.g;
|
||||
if( rhs.b > b) b = rhs.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @copydoc operator|=
|
||||
FASTLED_FORCE_INLINE CRGB& operator|= (fl::u8 d )
|
||||
{
|
||||
if( d > r) r = d;
|
||||
if( d > g) g = d;
|
||||
if( d > b) b = d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// "and" operator brings each channel down to the lower of the two values
|
||||
FASTLED_FORCE_INLINE CRGB& operator&= (const CRGB& rhs )
|
||||
{
|
||||
if( rhs.r < r) r = rhs.r;
|
||||
if( rhs.g < g) g = rhs.g;
|
||||
if( rhs.b < b) b = rhs.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @copydoc operator&=
|
||||
FASTLED_FORCE_INLINE CRGB& operator&= (fl::u8 d )
|
||||
{
|
||||
if( d < r) r = d;
|
||||
if( d < g) g = d;
|
||||
if( d < b) b = d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// This allows testing a CRGB for zero-ness
|
||||
constexpr explicit operator bool() const
|
||||
{
|
||||
return r || g || b;
|
||||
}
|
||||
|
||||
/// Converts a CRGB to a 32-bit color having an alpha of 255.
|
||||
constexpr explicit operator fl::u32() const
|
||||
{
|
||||
return fl::u32(0xff000000) |
|
||||
(fl::u32{r} << 16) |
|
||||
(fl::u32{g} << 8) |
|
||||
fl::u32{b};
|
||||
}
|
||||
|
||||
/// Invert each channel
|
||||
constexpr CRGB operator-() const
|
||||
{
|
||||
return CRGB(255 - r, 255 - g, 255 - b);
|
||||
}
|
||||
|
||||
#if (defined SmartMatrix_h || defined SmartMatrix3_h)
|
||||
/// Convert to an rgb24 object, used with the SmartMatrix library
|
||||
/// @see https://github.com/pixelmatix/SmartMatrix
|
||||
operator rgb24() const {
|
||||
rgb24 ret;
|
||||
ret.red = r;
|
||||
ret.green = g;
|
||||
ret.blue = b;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
fl::string toString() const;
|
||||
|
||||
/// Get the "luma" of a CRGB object. In other words, roughly how much
|
||||
/// light the CRGB pixel is putting out (from 0 to 255).
|
||||
fl::u8 getLuma() const;
|
||||
|
||||
|
||||
|
||||
/// Get the average of the R, G, and B values
|
||||
FASTLED_FORCE_INLINE fl::u8 getAverageLight() const;
|
||||
|
||||
/// Maximize the brightness of this CRGB object.
|
||||
/// This makes the individual color channels as bright as possible
|
||||
/// while keeping the same value differences between channels.
|
||||
/// @note This does not keep the same ratios between channels,
|
||||
/// just the same difference in absolute values.
|
||||
FASTLED_FORCE_INLINE void maximizeBrightness( fl::u8 limit = 255 ) {
|
||||
fl::u8 max = red;
|
||||
if( green > max) max = green;
|
||||
if( blue > max) max = blue;
|
||||
|
||||
// stop div/0 when color is black
|
||||
if(max > 0) {
|
||||
fl::u16 factor = ((fl::u16)(limit) * 256) / max;
|
||||
red = (red * factor) / 256;
|
||||
green = (green * factor) / 256;
|
||||
blue = (blue * factor) / 256;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the combined color adjustment to the LEDs at a given scale, color correction, and color temperature
|
||||
/// @param scale the scale value for the RGB data (i.e. brightness)
|
||||
/// @param colorCorrection color correction to apply
|
||||
/// @param colorTemperature color temperature to apply
|
||||
/// @returns a CRGB object representing the adjustment, including color correction and color temperature
|
||||
static CRGB computeAdjustment(fl::u8 scale, const CRGB & colorCorrection, const CRGB & colorTemperature);
|
||||
|
||||
/// Return a new CRGB object after performing a linear interpolation between this object and the passed in object
|
||||
CRGB lerp8( const CRGB& other, fract8 amountOf2) const;
|
||||
|
||||
/// @copydoc lerp8
|
||||
FASTLED_FORCE_INLINE CRGB lerp16( const CRGB& other, fract16 frac) const;
|
||||
/// Returns 0 or 1, depending on the lowest bit of the sum of the color components.
|
||||
FASTLED_FORCE_INLINE fl::u8 getParity()
|
||||
{
|
||||
fl::u8 sum = r + g + b;
|
||||
return (sum & 0x01);
|
||||
}
|
||||
|
||||
/// Adjusts the color in the smallest way possible
|
||||
/// so that the parity of the coloris now the desired value.
|
||||
/// This allows you to "hide" one bit of information in the color.
|
||||
///
|
||||
/// Ideally, we find one color channel which already
|
||||
/// has data in it, and modify just that channel by one.
|
||||
/// We don't want to light up a channel that's black
|
||||
/// if we can avoid it, and if the pixel is 'grayscale',
|
||||
/// (meaning that R==G==B), we modify all three channels
|
||||
/// at once, to preserve the neutral hue.
|
||||
///
|
||||
/// There's no such thing as a free lunch; in many cases
|
||||
/// this "hidden bit" may actually be visible, but this
|
||||
/// code makes reasonable efforts to hide it as much
|
||||
/// as is reasonably possible.
|
||||
///
|
||||
/// Also, an effort is made to make it such that
|
||||
/// repeatedly setting the parity to different values
|
||||
/// will not cause the color to "drift". Toggling
|
||||
/// the parity twice should generally result in the
|
||||
/// original color again.
|
||||
///
|
||||
FASTLED_FORCE_INLINE void setParity( fl::u8 parity)
|
||||
{
|
||||
fl::u8 curparity = getParity();
|
||||
|
||||
if( parity == curparity) return;
|
||||
|
||||
if( parity ) {
|
||||
// going 'up'
|
||||
if( (b > 0) && (b < 255)) {
|
||||
if( r == g && g == b) {
|
||||
++r;
|
||||
++g;
|
||||
}
|
||||
++b;
|
||||
} else if( (r > 0) && (r < 255)) {
|
||||
++r;
|
||||
} else if( (g > 0) && (g < 255)) {
|
||||
++g;
|
||||
} else {
|
||||
if( r == g && g == b) {
|
||||
r ^= 0x01;
|
||||
g ^= 0x01;
|
||||
}
|
||||
b ^= 0x01;
|
||||
}
|
||||
} else {
|
||||
// going 'down'
|
||||
if( b > 1) {
|
||||
if( r == g && g == b) {
|
||||
--r;
|
||||
--g;
|
||||
}
|
||||
--b;
|
||||
} else if( g > 1) {
|
||||
--g;
|
||||
} else if( r > 1) {
|
||||
--r;
|
||||
} else {
|
||||
if( r == g && g == b) {
|
||||
r ^= 0x01;
|
||||
g ^= 0x01;
|
||||
}
|
||||
b ^= 0x01;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined RGB colors
|
||||
typedef enum {
|
||||
AliceBlue=0xF0F8FF, ///< @htmlcolorblock{F0F8FF}
|
||||
Amethyst=0x9966CC, ///< @htmlcolorblock{9966CC}
|
||||
AntiqueWhite=0xFAEBD7, ///< @htmlcolorblock{FAEBD7}
|
||||
Aqua=0x00FFFF, ///< @htmlcolorblock{00FFFF}
|
||||
Aquamarine=0x7FFFD4, ///< @htmlcolorblock{7FFFD4}
|
||||
Azure=0xF0FFFF, ///< @htmlcolorblock{F0FFFF}
|
||||
Beige=0xF5F5DC, ///< @htmlcolorblock{F5F5DC}
|
||||
Bisque=0xFFE4C4, ///< @htmlcolorblock{FFE4C4}
|
||||
Black=0x000000, ///< @htmlcolorblock{000000}
|
||||
BlanchedAlmond=0xFFEBCD, ///< @htmlcolorblock{FFEBCD}
|
||||
Blue=0x0000FF, ///< @htmlcolorblock{0000FF}
|
||||
BlueViolet=0x8A2BE2, ///< @htmlcolorblock{8A2BE2}
|
||||
Brown=0xA52A2A, ///< @htmlcolorblock{A52A2A}
|
||||
BurlyWood=0xDEB887, ///< @htmlcolorblock{DEB887}
|
||||
CadetBlue=0x5F9EA0, ///< @htmlcolorblock{5F9EA0}
|
||||
Chartreuse=0x7FFF00, ///< @htmlcolorblock{7FFF00}
|
||||
Chocolate=0xD2691E, ///< @htmlcolorblock{D2691E}
|
||||
Coral=0xFF7F50, ///< @htmlcolorblock{FF7F50}
|
||||
CornflowerBlue=0x6495ED, ///< @htmlcolorblock{6495ED}
|
||||
Cornsilk=0xFFF8DC, ///< @htmlcolorblock{FFF8DC}
|
||||
Crimson=0xDC143C, ///< @htmlcolorblock{DC143C}
|
||||
Cyan=0x00FFFF, ///< @htmlcolorblock{00FFFF}
|
||||
DarkBlue=0x00008B, ///< @htmlcolorblock{00008B}
|
||||
DarkCyan=0x008B8B, ///< @htmlcolorblock{008B8B}
|
||||
DarkGoldenrod=0xB8860B, ///< @htmlcolorblock{B8860B}
|
||||
DarkGray=0xA9A9A9, ///< @htmlcolorblock{A9A9A9}
|
||||
DarkGrey=0xA9A9A9, ///< @htmlcolorblock{A9A9A9}
|
||||
DarkGreen=0x006400, ///< @htmlcolorblock{006400}
|
||||
DarkKhaki=0xBDB76B, ///< @htmlcolorblock{BDB76B}
|
||||
DarkMagenta=0x8B008B, ///< @htmlcolorblock{8B008B}
|
||||
DarkOliveGreen=0x556B2F, ///< @htmlcolorblock{556B2F}
|
||||
DarkOrange=0xFF8C00, ///< @htmlcolorblock{FF8C00}
|
||||
DarkOrchid=0x9932CC, ///< @htmlcolorblock{9932CC}
|
||||
DarkRed=0x8B0000, ///< @htmlcolorblock{8B0000}
|
||||
DarkSalmon=0xE9967A, ///< @htmlcolorblock{E9967A}
|
||||
DarkSeaGreen=0x8FBC8F, ///< @htmlcolorblock{8FBC8F}
|
||||
DarkSlateBlue=0x483D8B, ///< @htmlcolorblock{483D8B}
|
||||
DarkSlateGray=0x2F4F4F, ///< @htmlcolorblock{2F4F4F}
|
||||
DarkSlateGrey=0x2F4F4F, ///< @htmlcolorblock{2F4F4F}
|
||||
DarkTurquoise=0x00CED1, ///< @htmlcolorblock{00CED1}
|
||||
DarkViolet=0x9400D3, ///< @htmlcolorblock{9400D3}
|
||||
DeepPink=0xFF1493, ///< @htmlcolorblock{FF1493}
|
||||
DeepSkyBlue=0x00BFFF, ///< @htmlcolorblock{00BFFF}
|
||||
DimGray=0x696969, ///< @htmlcolorblock{696969}
|
||||
DimGrey=0x696969, ///< @htmlcolorblock{696969}
|
||||
DodgerBlue=0x1E90FF, ///< @htmlcolorblock{1E90FF}
|
||||
FireBrick=0xB22222, ///< @htmlcolorblock{B22222}
|
||||
FloralWhite=0xFFFAF0, ///< @htmlcolorblock{FFFAF0}
|
||||
ForestGreen=0x228B22, ///< @htmlcolorblock{228B22}
|
||||
Fuchsia=0xFF00FF, ///< @htmlcolorblock{FF00FF}
|
||||
Gainsboro=0xDCDCDC, ///< @htmlcolorblock{DCDCDC}
|
||||
GhostWhite=0xF8F8FF, ///< @htmlcolorblock{F8F8FF}
|
||||
Gold=0xFFD700, ///< @htmlcolorblock{FFD700}
|
||||
Goldenrod=0xDAA520, ///< @htmlcolorblock{DAA520}
|
||||
Gray=0x808080, ///< @htmlcolorblock{808080}
|
||||
Grey=0x808080, ///< @htmlcolorblock{808080}
|
||||
Green=0x008000, ///< @htmlcolorblock{008000}
|
||||
GreenYellow=0xADFF2F, ///< @htmlcolorblock{ADFF2F}
|
||||
Honeydew=0xF0FFF0, ///< @htmlcolorblock{F0FFF0}
|
||||
HotPink=0xFF69B4, ///< @htmlcolorblock{FF69B4}
|
||||
IndianRed=0xCD5C5C, ///< @htmlcolorblock{CD5C5C}
|
||||
Indigo=0x4B0082, ///< @htmlcolorblock{4B0082}
|
||||
Ivory=0xFFFFF0, ///< @htmlcolorblock{FFFFF0}
|
||||
Khaki=0xF0E68C, ///< @htmlcolorblock{F0E68C}
|
||||
Lavender=0xE6E6FA, ///< @htmlcolorblock{E6E6FA}
|
||||
LavenderBlush=0xFFF0F5, ///< @htmlcolorblock{FFF0F5}
|
||||
LawnGreen=0x7CFC00, ///< @htmlcolorblock{7CFC00}
|
||||
LemonChiffon=0xFFFACD, ///< @htmlcolorblock{FFFACD}
|
||||
LightBlue=0xADD8E6, ///< @htmlcolorblock{ADD8E6}
|
||||
LightCoral=0xF08080, ///< @htmlcolorblock{F08080}
|
||||
LightCyan=0xE0FFFF, ///< @htmlcolorblock{E0FFFF}
|
||||
LightGoldenrodYellow=0xFAFAD2, ///< @htmlcolorblock{FAFAD2}
|
||||
LightGreen=0x90EE90, ///< @htmlcolorblock{90EE90}
|
||||
LightGrey=0xD3D3D3, ///< @htmlcolorblock{D3D3D3}
|
||||
LightPink=0xFFB6C1, ///< @htmlcolorblock{FFB6C1}
|
||||
LightSalmon=0xFFA07A, ///< @htmlcolorblock{FFA07A}
|
||||
LightSeaGreen=0x20B2AA, ///< @htmlcolorblock{20B2AA}
|
||||
LightSkyBlue=0x87CEFA, ///< @htmlcolorblock{87CEFA}
|
||||
LightSlateGray=0x778899, ///< @htmlcolorblock{778899}
|
||||
LightSlateGrey=0x778899, ///< @htmlcolorblock{778899}
|
||||
LightSteelBlue=0xB0C4DE, ///< @htmlcolorblock{B0C4DE}
|
||||
LightYellow=0xFFFFE0, ///< @htmlcolorblock{FFFFE0}
|
||||
Lime=0x00FF00, ///< @htmlcolorblock{00FF00}
|
||||
LimeGreen=0x32CD32, ///< @htmlcolorblock{32CD32}
|
||||
Linen=0xFAF0E6, ///< @htmlcolorblock{FAF0E6}
|
||||
Magenta=0xFF00FF, ///< @htmlcolorblock{FF00FF}
|
||||
Maroon=0x800000, ///< @htmlcolorblock{800000}
|
||||
MediumAquamarine=0x66CDAA, ///< @htmlcolorblock{66CDAA}
|
||||
MediumBlue=0x0000CD, ///< @htmlcolorblock{0000CD}
|
||||
MediumOrchid=0xBA55D3, ///< @htmlcolorblock{BA55D3}
|
||||
MediumPurple=0x9370DB, ///< @htmlcolorblock{9370DB}
|
||||
MediumSeaGreen=0x3CB371, ///< @htmlcolorblock{3CB371}
|
||||
MediumSlateBlue=0x7B68EE, ///< @htmlcolorblock{7B68EE}
|
||||
MediumSpringGreen=0x00FA9A, ///< @htmlcolorblock{00FA9A}
|
||||
MediumTurquoise=0x48D1CC, ///< @htmlcolorblock{48D1CC}
|
||||
MediumVioletRed=0xC71585, ///< @htmlcolorblock{C71585}
|
||||
MidnightBlue=0x191970, ///< @htmlcolorblock{191970}
|
||||
MintCream=0xF5FFFA, ///< @htmlcolorblock{F5FFFA}
|
||||
MistyRose=0xFFE4E1, ///< @htmlcolorblock{FFE4E1}
|
||||
Moccasin=0xFFE4B5, ///< @htmlcolorblock{FFE4B5}
|
||||
NavajoWhite=0xFFDEAD, ///< @htmlcolorblock{FFDEAD}
|
||||
Navy=0x000080, ///< @htmlcolorblock{000080}
|
||||
OldLace=0xFDF5E6, ///< @htmlcolorblock{FDF5E6}
|
||||
Olive=0x808000, ///< @htmlcolorblock{808000}
|
||||
OliveDrab=0x6B8E23, ///< @htmlcolorblock{6B8E23}
|
||||
Orange=0xFFA500, ///< @htmlcolorblock{FFA500}
|
||||
OrangeRed=0xFF4500, ///< @htmlcolorblock{FF4500}
|
||||
Orchid=0xDA70D6, ///< @htmlcolorblock{DA70D6}
|
||||
PaleGoldenrod=0xEEE8AA, ///< @htmlcolorblock{EEE8AA}
|
||||
PaleGreen=0x98FB98, ///< @htmlcolorblock{98FB98}
|
||||
PaleTurquoise=0xAFEEEE, ///< @htmlcolorblock{AFEEEE}
|
||||
PaleVioletRed=0xDB7093, ///< @htmlcolorblock{DB7093}
|
||||
PapayaWhip=0xFFEFD5, ///< @htmlcolorblock{FFEFD5}
|
||||
PeachPuff=0xFFDAB9, ///< @htmlcolorblock{FFDAB9}
|
||||
Peru=0xCD853F, ///< @htmlcolorblock{CD853F}
|
||||
Pink=0xFFC0CB, ///< @htmlcolorblock{FFC0CB}
|
||||
Plaid=0xCC5533, ///< @htmlcolorblock{CC5533}
|
||||
Plum=0xDDA0DD, ///< @htmlcolorblock{DDA0DD}
|
||||
PowderBlue=0xB0E0E6, ///< @htmlcolorblock{B0E0E6}
|
||||
Purple=0x800080, ///< @htmlcolorblock{800080}
|
||||
Red=0xFF0000, ///< @htmlcolorblock{FF0000}
|
||||
RosyBrown=0xBC8F8F, ///< @htmlcolorblock{BC8F8F}
|
||||
RoyalBlue=0x4169E1, ///< @htmlcolorblock{4169E1}
|
||||
SaddleBrown=0x8B4513, ///< @htmlcolorblock{8B4513}
|
||||
Salmon=0xFA8072, ///< @htmlcolorblock{FA8072}
|
||||
SandyBrown=0xF4A460, ///< @htmlcolorblock{F4A460}
|
||||
SeaGreen=0x2E8B57, ///< @htmlcolorblock{2E8B57}
|
||||
Seashell=0xFFF5EE, ///< @htmlcolorblock{FFF5EE}
|
||||
Sienna=0xA0522D, ///< @htmlcolorblock{A0522D}
|
||||
Silver=0xC0C0C0, ///< @htmlcolorblock{C0C0C0}
|
||||
SkyBlue=0x87CEEB, ///< @htmlcolorblock{87CEEB}
|
||||
SlateBlue=0x6A5ACD, ///< @htmlcolorblock{6A5ACD}
|
||||
SlateGray=0x708090, ///< @htmlcolorblock{708090}
|
||||
SlateGrey=0x708090, ///< @htmlcolorblock{708090}
|
||||
Snow=0xFFFAFA, ///< @htmlcolorblock{FFFAFA}
|
||||
SpringGreen=0x00FF7F, ///< @htmlcolorblock{00FF7F}
|
||||
SteelBlue=0x4682B4, ///< @htmlcolorblock{4682B4}
|
||||
Tan=0xD2B48C, ///< @htmlcolorblock{D2B48C}
|
||||
Teal=0x008080, ///< @htmlcolorblock{008080}
|
||||
Thistle=0xD8BFD8, ///< @htmlcolorblock{D8BFD8}
|
||||
Tomato=0xFF6347, ///< @htmlcolorblock{FF6347}
|
||||
Turquoise=0x40E0D0, ///< @htmlcolorblock{40E0D0}
|
||||
Violet=0xEE82EE, ///< @htmlcolorblock{EE82EE}
|
||||
Wheat=0xF5DEB3, ///< @htmlcolorblock{F5DEB3}
|
||||
White=0xFFFFFF, ///< @htmlcolorblock{FFFFFF}
|
||||
WhiteSmoke=0xF5F5F5, ///< @htmlcolorblock{F5F5F5}
|
||||
Yellow=0xFFFF00, ///< @htmlcolorblock{FFFF00}
|
||||
YellowGreen=0x9ACD32, ///< @htmlcolorblock{9ACD32}
|
||||
|
||||
// LED RGB color that roughly approximates
|
||||
// the color of incandescent fairy lights,
|
||||
// assuming that you're using FastLED
|
||||
// color correction on your LEDs (recommended).
|
||||
FairyLight=0xFFE42D, ///< @htmlcolorblock{FFE42D}
|
||||
|
||||
// If you are using no color correction, use this
|
||||
FairyLightNCC=0xFF9D2A, ///< @htmlcolorblock{FFE42D}
|
||||
|
||||
// TCL Color Extensions - Essential additions for LED programming
|
||||
// These colors provide useful grayscale levels and color variants
|
||||
|
||||
// Essential grayscale levels (0-100 scale for precise dimming)
|
||||
Gray0=0x000000, ///< TCL grayscale 0% @htmlcolorblock{000000}
|
||||
Gray10=0x1A1A1A, ///< TCL grayscale 10% @htmlcolorblock{1A1A1A}
|
||||
Gray25=0x404040, ///< TCL grayscale 25% @htmlcolorblock{404040}
|
||||
Gray50=0x7F7F7F, ///< TCL grayscale 50% @htmlcolorblock{7F7F7F}
|
||||
Gray75=0xBFBFBF, ///< TCL grayscale 75% @htmlcolorblock{BFBFBF}
|
||||
Gray100=0xFFFFFF, ///< TCL grayscale 100% @htmlcolorblock{FFFFFF}
|
||||
|
||||
// Alternative grey spellings
|
||||
Grey0=0x000000, ///< TCL grayscale 0% @htmlcolorblock{000000}
|
||||
Grey10=0x1A1A1A, ///< TCL grayscale 10% @htmlcolorblock{1A1A1A}
|
||||
Grey25=0x404040, ///< TCL grayscale 25% @htmlcolorblock{404040}
|
||||
Grey50=0x7F7F7F, ///< TCL grayscale 50% @htmlcolorblock{7F7F7F}
|
||||
Grey75=0xBFBFBF, ///< TCL grayscale 75% @htmlcolorblock{BFBFBF}
|
||||
Grey100=0xFFFFFF, ///< TCL grayscale 100% @htmlcolorblock{FFFFFF}
|
||||
|
||||
// Primary color variants (1-4 intensity levels)
|
||||
Red1=0xFF0000, ///< TCL red variant 1 (brightest) @htmlcolorblock{FF0000}
|
||||
Red2=0xEE0000, ///< TCL red variant 2 @htmlcolorblock{EE0000}
|
||||
Red3=0xCD0000, ///< TCL red variant 3 @htmlcolorblock{CD0000}
|
||||
Red4=0x8B0000, ///< TCL red variant 4 (darkest) @htmlcolorblock{8B0000}
|
||||
|
||||
Green1=0x00FF00, ///< TCL green variant 1 (brightest) @htmlcolorblock{00FF00}
|
||||
Green2=0x00EE00, ///< TCL green variant 2 @htmlcolorblock{00EE00}
|
||||
Green3=0x00CD00, ///< TCL green variant 3 @htmlcolorblock{00CD00}
|
||||
Green4=0x008B00, ///< TCL green variant 4 (darkest) @htmlcolorblock{008B00}
|
||||
|
||||
Blue1=0x0000FF, ///< TCL blue variant 1 (brightest) @htmlcolorblock{0000FF}
|
||||
Blue2=0x0000EE, ///< TCL blue variant 2 @htmlcolorblock{0000EE}
|
||||
Blue3=0x0000CD, ///< TCL blue variant 3 @htmlcolorblock{0000CD}
|
||||
Blue4=0x00008B, ///< TCL blue variant 4 (darkest) @htmlcolorblock{00008B}
|
||||
|
||||
// Useful warm color variants for LED ambience
|
||||
Orange1=0xFFA500, ///< TCL orange variant 1 @htmlcolorblock{FFA500}
|
||||
Orange2=0xEE9A00, ///< TCL orange variant 2 @htmlcolorblock{EE9A00}
|
||||
Orange3=0xCD8500, ///< TCL orange variant 3 @htmlcolorblock{CD8500}
|
||||
Orange4=0x8B5A00, ///< TCL orange variant 4 @htmlcolorblock{8B5A00}
|
||||
|
||||
Yellow1=0xFFFF00, ///< TCL yellow variant 1 @htmlcolorblock{FFFF00}
|
||||
Yellow2=0xEEEE00, ///< TCL yellow variant 2 @htmlcolorblock{EEEE00}
|
||||
Yellow3=0xCDCD00, ///< TCL yellow variant 3 @htmlcolorblock{CDCD00}
|
||||
Yellow4=0x8B8B00, ///< TCL yellow variant 4 @htmlcolorblock{8B8B00}
|
||||
|
||||
// Popular LED colors for effects
|
||||
Cyan1=0x00FFFF, ///< TCL cyan variant 1 @htmlcolorblock{00FFFF}
|
||||
Cyan2=0x00EEEE, ///< TCL cyan variant 2 @htmlcolorblock{00EEEE}
|
||||
Cyan3=0x00CDCD, ///< TCL cyan variant 3 @htmlcolorblock{00CDCD}
|
||||
Cyan4=0x008B8B, ///< TCL cyan variant 4 @htmlcolorblock{008B8B}
|
||||
|
||||
Magenta1=0xFF00FF, ///< TCL magenta variant 1 @htmlcolorblock{FF00FF}
|
||||
Magenta2=0xEE00EE, ///< TCL magenta variant 2 @htmlcolorblock{EE00EE}
|
||||
Magenta3=0xCD00CD, ///< TCL magenta variant 3 @htmlcolorblock{CD00CD}
|
||||
Magenta4=0x8B008B, ///< TCL magenta variant 4 @htmlcolorblock{8B008B}
|
||||
|
||||
// Additional useful colors for LED programming
|
||||
VioletRed=0xD02090, ///< TCL violet red @htmlcolorblock{D02090}
|
||||
DeepPink1=0xFF1493, ///< TCL deep pink variant 1 @htmlcolorblock{FF1493}
|
||||
DeepPink2=0xEE1289, ///< TCL deep pink variant 2 @htmlcolorblock{EE1289}
|
||||
DeepPink3=0xCD1076, ///< TCL deep pink variant 3 @htmlcolorblock{CD1076}
|
||||
DeepPink4=0x8B0A50, ///< TCL deep pink variant 4 @htmlcolorblock{8B0A50}
|
||||
|
||||
Gold1=0xFFD700, ///< TCL gold variant 1 @htmlcolorblock{FFD700}
|
||||
Gold2=0xEEC900, ///< TCL gold variant 2 @htmlcolorblock{EEC900}
|
||||
Gold3=0xCDAD00, ///< TCL gold variant 3 @htmlcolorblock{CDAD00}
|
||||
Gold4=0x8B7500 ///< TCL gold variant 4 @htmlcolorblock{8B7500}
|
||||
|
||||
} HTMLColorCode;
|
||||
};
|
||||
|
||||
|
||||
/// Check if two CRGB objects have the same color data
|
||||
FASTLED_FORCE_INLINE bool operator== (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b);
|
||||
}
|
||||
|
||||
/// Check if two CRGB objects do *not* have the same color data
|
||||
FASTLED_FORCE_INLINE bool operator!= (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// Check if two CHSV objects have the same color data
|
||||
FASTLED_FORCE_INLINE bool operator== (const CHSV& lhs, const CHSV& rhs)
|
||||
{
|
||||
return (lhs.h == rhs.h) && (lhs.s == rhs.s) && (lhs.v == rhs.v);
|
||||
}
|
||||
|
||||
/// Check if two CHSV objects do *not* have the same color data
|
||||
FASTLED_FORCE_INLINE bool operator!= (const CHSV& lhs, const CHSV& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// Check if the sum of the color channels in one CRGB object is less than another
|
||||
FASTLED_FORCE_INLINE bool operator< (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
fl::u16 sl, sr;
|
||||
sl = lhs.r + lhs.g + lhs.b;
|
||||
sr = rhs.r + rhs.g + rhs.b;
|
||||
return sl < sr;
|
||||
}
|
||||
|
||||
/// Check if the sum of the color channels in one CRGB object is greater than another
|
||||
FASTLED_FORCE_INLINE bool operator> (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
fl::u16 sl, sr;
|
||||
sl = lhs.r + lhs.g + lhs.b;
|
||||
sr = rhs.r + rhs.g + rhs.b;
|
||||
return sl > sr;
|
||||
}
|
||||
|
||||
/// Check if the sum of the color channels in one CRGB object is greater than or equal to another
|
||||
FASTLED_FORCE_INLINE bool operator>= (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
fl::u16 sl, sr;
|
||||
sl = lhs.r + lhs.g + lhs.b;
|
||||
sr = rhs.r + rhs.g + rhs.b;
|
||||
return sl >= sr;
|
||||
}
|
||||
|
||||
/// Check if the sum of the color channels in one CRGB object is less than or equal to another
|
||||
FASTLED_FORCE_INLINE bool operator<= (const CRGB& lhs, const CRGB& rhs)
|
||||
{
|
||||
fl::u16 sl, sr;
|
||||
sl = lhs.r + lhs.g + lhs.b;
|
||||
sr = rhs.r + rhs.g + rhs.b;
|
||||
return sl <= sr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @copydoc CRGB::operator/=
|
||||
FASTLED_FORCE_INLINE CRGB operator/( const CRGB& p1, fl::u8 d)
|
||||
{
|
||||
return CRGB( p1.r/d, p1.g/d, p1.b/d);
|
||||
}
|
||||
|
||||
|
||||
/// Combine two CRGB objects, taking the smallest value of each channel
|
||||
FASTLED_FORCE_INLINE CRGB operator&( const CRGB& p1, const CRGB& p2)
|
||||
{
|
||||
return CRGB( p1.r < p2.r ? p1.r : p2.r,
|
||||
p1.g < p2.g ? p1.g : p2.g,
|
||||
p1.b < p2.b ? p1.b : p2.b);
|
||||
}
|
||||
|
||||
/// Combine two CRGB objects, taking the largest value of each channel
|
||||
FASTLED_FORCE_INLINE CRGB operator|( const CRGB& p1, const CRGB& p2)
|
||||
{
|
||||
return CRGB( p1.r > p2.r ? p1.r : p2.r,
|
||||
p1.g > p2.g ? p1.g : p2.g,
|
||||
p1.b > p2.b ? p1.b : p2.b);
|
||||
}
|
||||
|
||||
/// @copydoc CRGB::operator+=
|
||||
FASTLED_FORCE_INLINE CRGB operator+( const CRGB& p1, const CRGB& p2);
|
||||
|
||||
/// @copydoc CRGB::operator-=
|
||||
FASTLED_FORCE_INLINE CRGB operator-( const CRGB& p1, const CRGB& p2);
|
||||
|
||||
/// @copydoc CRGB::operator*=
|
||||
FASTLED_FORCE_INLINE CRGB operator*( const CRGB& p1, fl::u8 d);
|
||||
|
||||
/// Scale using CRGB::nscale8_video()
|
||||
FASTLED_FORCE_INLINE CRGB operator%( const CRGB& p1, fl::u8 d);
|
||||
|
||||
/// @} PixelTypes
|
||||
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
221
libraries/FastLED/src/crgb.hpp
Normal file
221
libraries/FastLED/src/crgb.hpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/// @file crgb.hpp
|
||||
/// Defines utility functions for the red, green, and blue (RGB) pixel struct
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "chsv.h"
|
||||
#include "crgb.h"
|
||||
#include "lib8tion.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/str.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
FL_DISABLE_WARNING_RETURN_TYPE
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
|
||||
#if FASTLED_IS_USING_NAMESPACE
|
||||
#define FUNCTION_SCALE8(a,b) FASTLED_NAMESPACE::scale8(a,b)
|
||||
#else
|
||||
#define FUNCTION_SCALE8(a,b) ::scale8(a,b)
|
||||
#endif
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::addToRGB (uint8_t d )
|
||||
{
|
||||
r = qadd8( r, d);
|
||||
g = qadd8( g, d);
|
||||
b = qadd8( b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::operator-= (const CRGB& rhs )
|
||||
{
|
||||
r = qsub8( r, rhs.r);
|
||||
g = qsub8( g, rhs.g);
|
||||
b = qsub8( b, rhs.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add a constant of '1' from each channel, saturating at 0xFF
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::operator++ ()
|
||||
{
|
||||
addToRGB(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @copydoc operator++
|
||||
FASTLED_FORCE_INLINE CRGB CRGB::operator++ (int )
|
||||
{
|
||||
CRGB retval(*this);
|
||||
++(*this);
|
||||
return retval;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::subtractFromRGB(uint8_t d)
|
||||
{
|
||||
r = qsub8( r, d);
|
||||
g = qsub8( g, d);
|
||||
b = qsub8( b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::operator*= (uint8_t d )
|
||||
{
|
||||
r = qmul8( r, d);
|
||||
g = qmul8( g, d);
|
||||
b = qmul8( b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::nscale8_video(uint8_t scaledown )
|
||||
{
|
||||
nscale8x3_video( r, g, b, scaledown);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::operator%= (uint8_t scaledown )
|
||||
{
|
||||
nscale8x3_video( r, g, b, scaledown);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::fadeLightBy (uint8_t fadefactor )
|
||||
{
|
||||
nscale8x3_video( r, g, b, 255 - fadefactor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Subtract a constant of '1' from each channel, saturating at 0x00
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::operator-- ()
|
||||
{
|
||||
subtractFromRGB(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @copydoc operator--
|
||||
FASTLED_FORCE_INLINE CRGB CRGB::operator-- (int )
|
||||
{
|
||||
CRGB retval(*this);
|
||||
--(*this);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
constexpr CRGB CRGB::nscale8_constexpr(const CRGB scaledown) const
|
||||
{
|
||||
return CRGB(
|
||||
scale8_constexpr(r, scaledown.r),
|
||||
scale8_constexpr(g, scaledown.g),
|
||||
scale8_constexpr(b, scaledown.b)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB& CRGB::nscale8 (const CRGB & scaledown )
|
||||
{
|
||||
r = FUNCTION_SCALE8(r, scaledown.r);
|
||||
g = FUNCTION_SCALE8(g, scaledown.g);
|
||||
b = FUNCTION_SCALE8(b, scaledown.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB CRGB::scale8 (uint8_t scaledown ) const
|
||||
{
|
||||
CRGB out = *this;
|
||||
nscale8x3( out.r, out.g, out.b, scaledown);
|
||||
return out;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB CRGB::scale8 (const CRGB & scaledown ) const
|
||||
{
|
||||
CRGB out;
|
||||
out.r = FUNCTION_SCALE8(r, scaledown.r);
|
||||
out.g = FUNCTION_SCALE8(g, scaledown.g);
|
||||
out.b = FUNCTION_SCALE8(b, scaledown.b);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
FASTLED_FORCE_INLINE uint8_t CRGB::getLuma( ) const {
|
||||
//Y' = 0.2126 R' + 0.7152 G' + 0.0722 B'
|
||||
// 54 183 18 (!)
|
||||
|
||||
uint8_t luma = scale8_LEAVING_R1_DIRTY( r, 54) + \
|
||||
scale8_LEAVING_R1_DIRTY( g, 183) + \
|
||||
scale8_LEAVING_R1_DIRTY( b, 18);
|
||||
cleanup_R1();
|
||||
return luma;
|
||||
}
|
||||
|
||||
FASTLED_FORCE_INLINE uint8_t CRGB::getAverageLight( ) const {
|
||||
#if FASTLED_SCALE8_FIXED == 1
|
||||
const uint8_t eightyfive = 85;
|
||||
#else
|
||||
const uint8_t eightyfive = 86;
|
||||
#endif
|
||||
uint8_t avg = scale8_LEAVING_R1_DIRTY( r, eightyfive) + \
|
||||
scale8_LEAVING_R1_DIRTY( g, eightyfive) + \
|
||||
scale8_LEAVING_R1_DIRTY( b, eightyfive);
|
||||
cleanup_R1();
|
||||
return avg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FASTLED_FORCE_INLINE CRGB CRGB::lerp16( const CRGB& other, fract16 frac) const
|
||||
{
|
||||
CRGB ret;
|
||||
|
||||
ret.r = lerp16by16(r<<8,other.r<<8,frac)>>8;
|
||||
ret.g = lerp16by16(g<<8,other.g<<8,frac)>>8;
|
||||
ret.b = lerp16by16(b<<8,other.b<<8,frac)>>8;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// @copydoc CRGB::operator+=
|
||||
FASTLED_FORCE_INLINE CRGB operator+( const CRGB& p1, const CRGB& p2)
|
||||
{
|
||||
return CRGB( qadd8( p1.r, p2.r),
|
||||
qadd8( p1.g, p2.g),
|
||||
qadd8( p1.b, p2.b));
|
||||
}
|
||||
|
||||
/// @copydoc CRGB::operator-=
|
||||
FASTLED_FORCE_INLINE CRGB operator-( const CRGB& p1, const CRGB& p2)
|
||||
{
|
||||
return CRGB( qsub8( p1.r, p2.r),
|
||||
qsub8( p1.g, p2.g),
|
||||
qsub8( p1.b, p2.b));
|
||||
}
|
||||
|
||||
/// @copydoc CRGB::operator*=
|
||||
FASTLED_FORCE_INLINE CRGB operator*( const CRGB& p1, uint8_t d)
|
||||
{
|
||||
return CRGB( qmul8( p1.r, d),
|
||||
qmul8( p1.g, d),
|
||||
qmul8( p1.b, d));
|
||||
}
|
||||
|
||||
/// Scale using CRGB::nscale8_video()
|
||||
FASTLED_FORCE_INLINE CRGB operator%( const CRGB& p1, uint8_t d)
|
||||
{
|
||||
CRGB retval( p1);
|
||||
retval.nscale8_video( d);
|
||||
return retval;
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#undef FUNCTION_SCALE8
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
18
libraries/FastLED/src/dither_mode.h
Normal file
18
libraries/FastLED/src/dither_mode.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/// @file dither_mode.h
|
||||
/// Declares dithering options and types
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
/// Disable dithering
|
||||
#define DISABLE_DITHER 0x00
|
||||
/// Enable dithering using binary dithering (only option)
|
||||
#define BINARY_DITHER 0x01
|
||||
/// The dither setting, either DISABLE_DITHER or BINARY_DITHER
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
typedef fl::u8 EDitherMode;
|
||||
FASTLED_NAMESPACE_END
|
||||
88
libraries/FastLED/src/dmx.h
Normal file
88
libraries/FastLED/src/dmx.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/// @file dmx.h
|
||||
/// Defines the DMX512-based LED controllers.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
/// @addtogroup Chipsets
|
||||
/// @{
|
||||
|
||||
/// @addtogroup ClocklessChipsets
|
||||
/// @{
|
||||
|
||||
#if defined(DmxSimple_h) || defined(FASTLED_DOXYGEN)
|
||||
#include <DmxSimple.h>
|
||||
|
||||
/// Flag set when the DmxSimple library is included
|
||||
#define HAS_DMX_SIMPLE
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// DMX512 based LED controller class, using the DmxSimple library
|
||||
/// @tparam DATA_PIN the data pin for the output of the DMX bus
|
||||
/// @tparam RGB_ORDER the RGB ordering for these LEDs
|
||||
/// @see https://www.pjrc.com/teensy/td_libs_DmxSimple.html
|
||||
/// @see https://github.com/PaulStoffregen/DmxSimple
|
||||
/// @see https://en.wikipedia.org/wiki/DMX512
|
||||
template <uint8_t DATA_PIN, EOrder RGB_ORDER = RGB> class DMXSimpleController : public CPixelLEDController<RGB_ORDER> {
|
||||
public:
|
||||
/// Initialize the LED controller
|
||||
virtual void init() { DmxSimple.usePin(DATA_PIN); }
|
||||
|
||||
protected:
|
||||
/// @copydoc CPixelLEDController::showPixels()
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels) {
|
||||
int iChannel = 1;
|
||||
while(pixels.has(1)) {
|
||||
DmxSimple.write(iChannel++, pixels.loadAndScale0());
|
||||
DmxSimple.write(iChannel++, pixels.loadAndScale1());
|
||||
DmxSimple.write(iChannel++, pixels.loadAndScale2());
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(DmxSerial_h) || defined(FASTLED_DOXYGEN)
|
||||
#include <DMXSerial.h>
|
||||
|
||||
/// Flag set when the DMXSerial library is included
|
||||
#define HAS_DMX_SERIAL
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// DMX512 based LED controller class, using the DMXSerial library
|
||||
/// @tparam RGB_ORDER the RGB ordering for these LEDs
|
||||
/// @see http://www.mathertel.de/Arduino/DMXSerial.aspx
|
||||
/// @see https://github.com/mathertel/DMXSerial
|
||||
/// @see https://en.wikipedia.org/wiki/DMX512
|
||||
template <EOrder RGB_ORDER = RGB> class DMXSerialController : public CPixelLEDController<RGB_ORDER> {
|
||||
public:
|
||||
/// Initialize the LED controller
|
||||
virtual void init() { DMXSerial.init(DMXController); }
|
||||
|
||||
/// @copydoc CPixelLEDController::showPixels()
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels) {
|
||||
int iChannel = 1;
|
||||
while(pixels.has(1)) {
|
||||
DMXSerial.write(iChannel++, pixels.loadAndScale0());
|
||||
DMXSerial.write(iChannel++, pixels.loadAndScale1());
|
||||
DMXSerial.write(iChannel++, pixels.loadAndScale2());
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
/// @} DMXControllers
|
||||
/// @} Chipsets
|
||||
|
||||
#endif
|
||||
|
||||
24
libraries/FastLED/src/eorder.h
Normal file
24
libraries/FastLED/src/eorder.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/// @file eorder.h
|
||||
/// Defines color channel ordering enumerations
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/eorder.h"
|
||||
|
||||
// Global aliases for backward compatibility
|
||||
using EOrder = fl::EOrder;
|
||||
using EOrderW = fl::EOrderW;
|
||||
|
||||
// Bring enum values into global scope
|
||||
using fl::RGB;
|
||||
using fl::RBG;
|
||||
using fl::GRB;
|
||||
using fl::GBR;
|
||||
using fl::BRG;
|
||||
using fl::BGR;
|
||||
|
||||
using fl::W3;
|
||||
using fl::W2;
|
||||
using fl::W1;
|
||||
using fl::W0;
|
||||
using fl::WDefault;
|
||||
88
libraries/FastLED/src/fastled_config.h
Normal file
88
libraries/FastLED/src/fastled_config.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastled_config.h
|
||||
/// Contains definitions that can be used to configure FastLED at compile time
|
||||
|
||||
/// @def FASTLED_FORCE_SOFTWARE_PINS
|
||||
/// Use this option only for debugging pin access and forcing software pin access. Forces use of `digitalWrite()`
|
||||
/// methods for pin access vs. direct hardware port access.
|
||||
/// @note Software pin access only works in Arduino-based environments.
|
||||
// #define FASTLED_FORCE_SOFTWARE_PINS
|
||||
|
||||
/// @def FASTLED_FORCE_SOFTWARE_SPI
|
||||
/// Use this option only for debugging bitbang'd SPI access or to work around bugs in hardware
|
||||
/// SPI access. Forces use of bit-banged SPI, even on pins that have hardware SPI available.
|
||||
// #define FASTLED_FORCE_SOFTWARE_SPI
|
||||
|
||||
/// @def FASTLED_ALLOW_INTERRUPTS
|
||||
/// Use this to force FastLED to allow interrupts in the clockless chipsets (or to force it to
|
||||
/// disallow), overriding the default on platforms that support this. Set the value to 1 to
|
||||
/// allow interrupts or 0 to disallow them.
|
||||
// #define FASTLED_ALLOW_INTERRUPTS 1
|
||||
// #define FASTLED_ALLOW_INTERRUPTS 0
|
||||
|
||||
/// @def FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW
|
||||
/// Use this to allow some integer overflows/underflows in the inoise() functions.
|
||||
/// The original implementions allowed this, and had some discontinuties in the noise
|
||||
/// output. It's technically an implementation bug, and was fixed, but you may wish
|
||||
/// to preserve the old look and feel of the inoise() functions in your existing animations.
|
||||
/// The default is 0: NO overflow, and 'continuous' noise output, aka the fixed way.
|
||||
// #define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 0
|
||||
// #define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 1
|
||||
|
||||
/// @def FASTLED_SCALE8_FIXED
|
||||
/// Use this to toggle whether or not to use the "fixed" FastLED scale8(). The initial scale8()
|
||||
/// had a problem where scale8(255,255) would give you 254. This is now fixed, and that
|
||||
/// fix is enabled by default. However, if for some reason you have code that is not
|
||||
/// working right as a result of this (e.g. code that was expecting the old scale8() behavior)
|
||||
/// you can disable it here.
|
||||
#define FASTLED_SCALE8_FIXED 1
|
||||
// #define FASTLED_SCALE8_FIXED 0
|
||||
|
||||
/// @def FASTLED_BLEND_FIXED
|
||||
/// Use this to toggle whether to use "fixed" FastLED pixel blending, including ColorFromPalette.
|
||||
/// The prior pixel blend functions had integer-rounding math errors that led to
|
||||
/// small errors being inadvertently added to the low bits of blended colors, including colors
|
||||
/// retrieved from color palettes using LINEAR_BLEND. This is now fixed, and the
|
||||
/// fix is enabled by default. However, if for some reason you wish to run with the old
|
||||
/// blending, including the integer rounding and color errors, you can disable the bugfix here.
|
||||
#define FASTLED_BLEND_FIXED 1
|
||||
// #define FASTLED_BLEND_FIXED 0
|
||||
|
||||
/// @def FASTLED_NOISE_FIXED
|
||||
/// Use this to toggle whether to use "fixed" FastLED 8-bit and 16-bit noise functions.
|
||||
/// The prior noise functions had some math errors that led to "discontinuities" in the
|
||||
/// output, which by definition should be smooth and continuous. The bug led to
|
||||
/// noise function output that had "edges" and glitches in it. This is now fixed, and the
|
||||
/// fix is enabled by default. However, if for some reason you wish to run with the old
|
||||
/// noise code, including the glitches, you can disable the bugfix here.
|
||||
#define FASTLED_NOISE_FIXED 1
|
||||
//#define FASTLED_NOISE_FIXED 0
|
||||
|
||||
/// @def FASTLED_INTERRUPT_RETRY_COUNT
|
||||
/// Use this to determine how many times FastLED will attempt to re-transmit a frame if interrupted
|
||||
/// for too long by interrupts.
|
||||
#ifndef FASTLED_INTERRUPT_RETRY_COUNT
|
||||
#define FASTLED_INTERRUPT_RETRY_COUNT 2
|
||||
#endif
|
||||
|
||||
/// @def FASTLED_USE_GLOBAL_BRIGHTNESS
|
||||
/// Use this toggle to enable global brightness in contollers that support is (e.g. ADA102 and SK9822).
|
||||
/// It changes how color scaling works and uses global brightness before scaling down color values.
|
||||
/// This enables much more accurate color control on low brightness settings.
|
||||
//#define FASTLED_USE_GLOBAL_BRIGHTNESS 1
|
||||
|
||||
|
||||
// The defines are used for Doxygen documentation generation.
|
||||
// They're commented out above and repeated here so the Doxygen parser
|
||||
// will be able to find them. They will not affect your own configuration,
|
||||
// and you do *NOT* need to modify them.
|
||||
#ifdef FASTLED_DOXYGEN
|
||||
#define FASTLED_FORCE_SOFTWARE_PINS
|
||||
#define FASTLED_FORCE_SOFTWARE_SPI
|
||||
#define FASTLED_ALLOW_INTERRUPTS
|
||||
#define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 0
|
||||
#define FASTLED_INTERRUPT_RETRY_COUNT 2
|
||||
#define FASTLED_USE_GLOBAL_BRIGHTNESS 0
|
||||
#endif
|
||||
|
||||
221
libraries/FastLED/src/fastled_delay.h
Normal file
221
libraries/FastLED/src/fastled_delay.h
Normal file
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __INC_FL_DELAY_H
|
||||
#define __INC_FL_DELAY_H
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/types.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
/// @file fastled_delay.h
|
||||
/// Utility functions and classes for managing delay cycles
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
#if (!defined(NO_MINIMUM_WAIT) || (NO_MINIMUM_WAIT==0))
|
||||
|
||||
/// Class to ensure that a minimum amount of time has kicked since the last time run - and delay if not enough time has passed yet.
|
||||
/// @tparam WAIT The amount of time to wait, in microseconds
|
||||
template<int WAIT> class CMinWait {
|
||||
/// Timestamp of the last time this was run, in microseconds
|
||||
fl::u16 mLastMicros;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
CMinWait() { mLastMicros = 0; }
|
||||
|
||||
/// Blocking delay until WAIT time since mark() has passed
|
||||
void wait() {
|
||||
fl::u16 diff;
|
||||
do {
|
||||
diff = (micros() & 0xFFFF) - mLastMicros;
|
||||
} while(diff < WAIT);
|
||||
}
|
||||
|
||||
/// Reset the timestamp that marks the start of the wait period
|
||||
void mark() { mLastMicros = micros() & 0xFFFF; }
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// if you keep your own FPS (and therefore don't call show() too quickly for pixels to latch), you may not want a minimum wait.
|
||||
template<int WAIT> class CMinWait {
|
||||
public:
|
||||
CMinWait() { }
|
||||
void wait() { }
|
||||
void mark() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// @name Clock cycle counted delay loop
|
||||
///
|
||||
/// @{
|
||||
|
||||
// Default is now just 'nop', with special case for AVR
|
||||
|
||||
// ESP32 core has it's own definition of NOP, so undef it first
|
||||
#ifdef ESP32
|
||||
#undef NOP
|
||||
#undef NOP2
|
||||
#endif
|
||||
|
||||
#if defined(__AVR__)
|
||||
# define FL_NOP __asm__ __volatile__ ("cp r0,r0\n");
|
||||
# define FL_NOP2 __asm__ __volatile__ ("rjmp .+0");
|
||||
#else
|
||||
/// Single no operation ("no-op") instruction for delay
|
||||
# define FL_NOP __asm__ __volatile__ ("nop\n");
|
||||
/// Double no operation ("no-op") instruction for delay
|
||||
# define FL_NOP2 __asm__ __volatile__ ("nop\n\t nop\n");
|
||||
#endif
|
||||
|
||||
// predeclaration to not upset the compiler
|
||||
|
||||
|
||||
/// Delay N clock cycles.
|
||||
/// @tparam CYCLES the number of clock cycles to delay
|
||||
/// @note No delay is applied if CYCLES is less than or equal to zero.
|
||||
template<fl::cycle_t CYCLES> inline void delaycycles();
|
||||
|
||||
/// A variant of ::delaycycles that will always delay
|
||||
/// at least one cycle.
|
||||
template<fl::cycle_t CYCLES> inline void delaycycles_min1() {
|
||||
delaycycles<1>();
|
||||
delaycycles<CYCLES-1>();
|
||||
}
|
||||
|
||||
|
||||
// TODO: ARM version of _delaycycles_
|
||||
|
||||
// usable definition
|
||||
#if defined(FASTLED_AVR)
|
||||
// worker template - this will nop for LOOP * 3 + PAD cycles total
|
||||
template<int LOOP, fl::cycle_t PAD> inline void _delaycycles_AVR() {
|
||||
delaycycles<PAD>();
|
||||
// the loop below is 3 cycles * LOOP. the LDI is one cycle,
|
||||
// the DEC is 1 cycle, the BRNE is 2 cycles if looping back and
|
||||
// 1 if not (the LDI balances out the BRNE being 1 cycle on exit)
|
||||
__asm__ __volatile__ (
|
||||
" LDI R16, %0\n"
|
||||
"L_%=: DEC R16\n"
|
||||
" BRNE L_%=\n"
|
||||
: /* no outputs */
|
||||
: "M" (LOOP)
|
||||
: "r16"
|
||||
);
|
||||
}
|
||||
|
||||
template<fl::cycle_t CYCLES> FASTLED_FORCE_INLINE void delaycycles() {
|
||||
_delaycycles_AVR<CYCLES / 3, CYCLES % 3>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else
|
||||
// template<int LOOP, fl::cycle_t PAD> inline void _delaycycles_ARM() {
|
||||
// delaycycles<PAD>();
|
||||
// // the loop below is 3 cycles * LOOP. the LDI is one cycle,
|
||||
// // the DEC is 1 cycle, the BRNE is 2 cycles if looping back and
|
||||
// // 1 if not (the LDI balances out the BRNE being 1 cycle on exit)
|
||||
// __asm__ __volatile__ (
|
||||
// " mov.w r9, %0\n"
|
||||
// "L_%=: subs.w r9, r9, #1\n"
|
||||
// " bne.n L_%=\n"
|
||||
// : /* no outputs */
|
||||
// : "M" (LOOP)
|
||||
// : "r9"
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
template<fl::cycle_t CYCLES> FASTLED_FORCE_INLINE void delaycycles() {
|
||||
// _delaycycles_ARM<CYCLES / 3, CYCLES % 3>();
|
||||
FL_NOP; delaycycles<CYCLES-1>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// pre-instantiations for values small enough to not need the loop, as well as sanity holders
|
||||
// for some negative values.
|
||||
|
||||
// These are hidden from Doxygen because they match the expected behavior of the class.
|
||||
/// @cond
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-10>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-9>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-8>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-7>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-6>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-5>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-4>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-3>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-2>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<-1>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<0>() {}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<1>() {FL_NOP;}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<2>() {FL_NOP2;}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<3>() {FL_NOP;FL_NOP2;}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<4>() {FL_NOP2;FL_NOP2;}
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<5>() {FL_NOP2;FL_NOP2;FL_NOP;}
|
||||
#if defined(ESP32)
|
||||
template<> FASTLED_FORCE_INLINE void delaycycles<4294966398>() {
|
||||
// specialization for a gigantic amount of cycles, apparently this is needed
|
||||
// or esp32 will blow the stack with cycles = 4294966398.
|
||||
const fl::u32 termination = 4294966398 / 10;
|
||||
const fl::u32 remainder = 4294966398 % 10;
|
||||
for (fl::u32 i = 0; i < termination; i++) {
|
||||
FL_NOP; FL_NOP; FL_NOP; FL_NOP; FL_NOP;
|
||||
FL_NOP; FL_NOP; FL_NOP; FL_NOP; FL_NOP;
|
||||
}
|
||||
|
||||
// remainder
|
||||
switch (remainder) {
|
||||
case 9: FL_NOP;
|
||||
case 8: FL_NOP;
|
||||
case 7: FL_NOP;
|
||||
case 6: FL_NOP;
|
||||
case 5: FL_NOP;
|
||||
case 4: FL_NOP;
|
||||
case 3: FL_NOP;
|
||||
case 2: FL_NOP;
|
||||
case 1: FL_NOP;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/// @endcond
|
||||
|
||||
/// @}
|
||||
|
||||
|
||||
/// @name Some timing related macros/definitions
|
||||
/// @{
|
||||
|
||||
// Macro to convert from nano-seconds to clocks and clocks to nano-seconds
|
||||
// #define NS(_NS) (_NS / (1000 / (F_CPU / 1000000L)))
|
||||
|
||||
/// CPU speed, in megahertz (MHz)
|
||||
#define F_CPU_MHZ (F_CPU / 1000000L)
|
||||
|
||||
// #define NS(_NS) ( (_NS * (F_CPU / 1000000L))) / 1000
|
||||
|
||||
/// Convert from nanoseconds to number of clock cycles
|
||||
#define NS(_NS) (((_NS * F_CPU_MHZ) + 999) / 1000)
|
||||
/// Convert from number of clock cycles to microseconds
|
||||
#define CLKS_TO_MICROS(_CLKS) ((long)(_CLKS)) / (F_CPU / 1000000L)
|
||||
|
||||
/// Macro for making sure there's enough time available
|
||||
#define NO_TIME(A, B, C) (NS(A) < 3 || NS(B) < 3 || NS(C) < 6)
|
||||
|
||||
/// @}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
111
libraries/FastLED/src/fastled_progmem.h
Normal file
111
libraries/FastLED/src/fastled_progmem.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
#if defined(__EMSCRIPTEN__) || defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL)
|
||||
#include "platforms/null_progmem.h"
|
||||
#elif defined(ESP8266)
|
||||
#include "platforms/esp/8266/progmem_esp8266.h"
|
||||
#else
|
||||
|
||||
|
||||
|
||||
|
||||
/// @file fastled_progmem.h
|
||||
/// Wrapper definitions to allow seamless use of PROGMEM in environments that have it
|
||||
///
|
||||
/// This is a compatibility layer for devices that do or don't
|
||||
/// have "PROGMEM" and the associated pgm_ accessors.
|
||||
///
|
||||
/// If a platform supports PROGMEM, it should define
|
||||
/// `FASTLED_USE_PROGMEM` as 1, otherwise FastLED will
|
||||
/// fall back to NOT using PROGMEM.
|
||||
///
|
||||
/// Whether or not pgmspace.h is \#included is separately
|
||||
/// controllable by FASTLED_INCLUDE_PGMSPACE, if needed.
|
||||
|
||||
|
||||
|
||||
// This block is used for Doxygen documentation generation,
|
||||
// so that the Doxygen parser will be able to find the macros
|
||||
// included without a defined platform
|
||||
#ifdef FASTLED_DOXYGEN
|
||||
#define FASTLED_USE_PROGMEM 1
|
||||
#endif
|
||||
|
||||
/// @def FASTLED_USE_PROGMEM
|
||||
/// Determine whether the current platform supports PROGMEM.
|
||||
/// If FASTLED_USE_PROGMEM is 1, we'll map FL_PROGMEM
|
||||
/// and the FL_PGM_* accessors to the Arduino equivalents.
|
||||
|
||||
|
||||
#if (FASTLED_USE_PROGMEM == 1) || defined(FASTLED_DOXYGEN)
|
||||
#ifndef FASTLED_INCLUDE_PGMSPACE
|
||||
#define FASTLED_INCLUDE_PGMSPACE 1
|
||||
#endif
|
||||
|
||||
#if FASTLED_INCLUDE_PGMSPACE == 1
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
|
||||
/// PROGMEM keyword for storage
|
||||
#define FL_PROGMEM PROGMEM
|
||||
|
||||
|
||||
/// @name PROGMEM Read Functions
|
||||
/// Functions for reading data from PROGMEM memory.
|
||||
///
|
||||
/// Note that only the "near" memory wrappers are provided.
|
||||
/// If you're using "far" memory, you already have
|
||||
/// portability issues to work through, but you could
|
||||
/// add more support here if needed.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Read a byte (8-bit) from PROGMEM memory
|
||||
#define FL_PGM_READ_BYTE_NEAR(x) (pgm_read_byte_near(x))
|
||||
/// Read a word (16-bit) from PROGMEM memory
|
||||
#define FL_PGM_READ_WORD_NEAR(x) (pgm_read_word_near(x))
|
||||
/// Read a double word (32-bit) from PROGMEM memory
|
||||
#define FL_PGM_READ_DWORD_NEAR(x) (pgm_read_dword_near(x))
|
||||
|
||||
/// @} PROGMEM
|
||||
|
||||
// Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
|
||||
#if __GNUC__ < 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ < 6))
|
||||
#ifdef FASTLED_AVR
|
||||
#ifdef PROGMEM
|
||||
#undef PROGMEM
|
||||
#define PROGMEM __attribute__((section(".progmem.data")))
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#else
|
||||
// If FASTLED_USE_PROGMEM is 0 or undefined,
|
||||
// we'll use regular memory (RAM) access.
|
||||
|
||||
#include "platforms/null_progmem.h"
|
||||
|
||||
#endif
|
||||
|
||||
/// @def FL_ALIGN_PROGMEM
|
||||
/// Helps to force 4-byte alignment for platforms with unaligned access
|
||||
///
|
||||
/// On some platforms, most notably ARM M0, unaligned access
|
||||
/// to 'PROGMEM' for multibyte values (e.g. read dword) is
|
||||
/// not allowed and causes a crash. This macro can help
|
||||
/// force 4-byte alignment as needed. The FastLED gradient
|
||||
/// palette code uses 'read dword', and now uses this macro
|
||||
/// to make sure that gradient palettes are 4-byte aligned.
|
||||
|
||||
#ifndef FL_ALIGN_PROGMEM
|
||||
#if defined(FASTLED_ARM) || defined(ESP32) || defined(FASTLED_DOXYGEN)
|
||||
#define FL_ALIGN_PROGMEM __attribute__ ((aligned (4)))
|
||||
#else
|
||||
#define FL_ALIGN_PROGMEM
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // defined(__EMSCRIPTEN__) || defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL)
|
||||
475
libraries/FastLED/src/fastpin.h
Normal file
475
libraries/FastLED/src/fastpin.h
Normal file
@@ -0,0 +1,475 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __INC_FASTPIN_H
|
||||
#define __INC_FASTPIN_H
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
#include "led_sysdefs.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/register.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wignored-qualifiers"
|
||||
#ifdef ESP32
|
||||
// Get rid of the endless volatile warnings in ESP32
|
||||
#pragma GCC diagnostic ignored "-Wpragmas"
|
||||
#pragma GCC diagnostic ignored "-Wvolatile"
|
||||
#endif
|
||||
|
||||
/// @file fastpin.h
|
||||
/// Class base definitions for defining fast pin access
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// Constant for "not a pin"
|
||||
/// @todo Unused, remove?
|
||||
#define NO_PIN 255
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Pin access class - needs to tune for various platforms (naive fallback solution?)
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Abstract class for "selectable" things
|
||||
class Selectable {
|
||||
public:
|
||||
#ifndef __AVR__
|
||||
virtual ~Selectable() {}
|
||||
#endif
|
||||
virtual void select() = 0; ///< Select this object
|
||||
virtual void release() = 0; ///< Release this object
|
||||
virtual bool isSelected() = 0; ///< Check if this object is currently selected
|
||||
};
|
||||
|
||||
#if defined(FASTLED_STUB_IMPL) || defined(__EMSCRIPTEN__)
|
||||
|
||||
|
||||
class Pin : public Selectable {
|
||||
|
||||
|
||||
void _init() {
|
||||
}
|
||||
|
||||
public:
|
||||
Pin(int pin) { FL_UNUSED(pin); }
|
||||
|
||||
|
||||
void setPin(int pin) { FL_UNUSED(pin); }
|
||||
|
||||
typedef volatile RwReg * port_ptr_t;
|
||||
typedef RwReg port_t;
|
||||
|
||||
inline void setOutput() { /* NOOP */ }
|
||||
inline void setInput() { /* NOOP */ }
|
||||
inline void setInputPullup() { /* NOOP */ }
|
||||
|
||||
|
||||
inline void hi() __attribute__ ((always_inline)) {}
|
||||
/// Set the pin state to `LOW`
|
||||
inline void lo() __attribute__ ((always_inline)) {}
|
||||
|
||||
|
||||
inline void strobe() __attribute__ ((always_inline)) { }
|
||||
inline void toggle() __attribute__ ((always_inline)) { }
|
||||
|
||||
inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { FL_UNUSED(port); }
|
||||
inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { FL_UNUSED(port); }
|
||||
inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { FL_UNUSED(val); }
|
||||
|
||||
inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { FL_UNUSED(port); FL_UNUSED(val); }
|
||||
|
||||
port_t hival() __attribute__ ((always_inline)) { return 0; }
|
||||
port_t loval() __attribute__ ((always_inline)) { return 0; }
|
||||
port_ptr_t port() __attribute__ ((always_inline)) {
|
||||
static volatile RwReg port = 0;
|
||||
return &port;
|
||||
}
|
||||
port_t mask() __attribute__ ((always_inline)) { return 0xff; }
|
||||
|
||||
virtual void select() override { hi(); }
|
||||
virtual void release() override { lo(); }
|
||||
virtual bool isSelected() override { return true; }
|
||||
};
|
||||
|
||||
class OutputPin : public Pin {
|
||||
public:
|
||||
OutputPin(int pin) : Pin(pin) { setOutput(); }
|
||||
};
|
||||
|
||||
class InputPin : public Pin {
|
||||
public:
|
||||
InputPin(int pin) : Pin(pin) { setInput(); }
|
||||
};
|
||||
|
||||
#elif !defined(FASTLED_NO_PINMAP)
|
||||
|
||||
/// Naive fallback solution for low level pin access
|
||||
class Pin : public Selectable {
|
||||
volatile RwReg *mPort; ///< Output register for the pin
|
||||
volatile RoReg *mInPort; ///< Input register for the pin
|
||||
RwReg mPinMask; ///< Bitmask for the pin within its register
|
||||
fl::u8 mPin; ///< Arduino digital pin number
|
||||
|
||||
/// Initialize the class by retrieving the register
|
||||
/// pointers and bitmask.
|
||||
void _init() {
|
||||
mPinMask = digitalPinToBitMask(mPin);
|
||||
mPort = (volatile RwReg*)portOutputRegister(digitalPinToPort(mPin));
|
||||
mInPort = (volatile RoReg*)portInputRegister(digitalPinToPort(mPin));
|
||||
}
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
/// @param pin Arduino digital pin number
|
||||
Pin(int pin) : mPin(pin) { _init(); }
|
||||
#ifndef __AVR__
|
||||
virtual ~Pin() {} // Shut up the compiler warning, but don't steal bytes from AVR.
|
||||
#endif
|
||||
|
||||
typedef volatile RwReg * port_ptr_t; ///< type for a pin read/write register, volatile
|
||||
typedef RwReg port_t; ///< type for a pin read/write register, non-volatile
|
||||
|
||||
/// Set the pin mode as `OUTPUT`
|
||||
inline void setOutput() { pinMode(mPin, OUTPUT); }
|
||||
|
||||
/// Set the pin mode as `INPUT`
|
||||
inline void setInput() { pinMode(mPin, INPUT); }
|
||||
|
||||
inline void setInputPullup() { pinMode(mPin, INPUT_PULLUP); }
|
||||
|
||||
/// Set the pin state to `HIGH`
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; }
|
||||
/// Set the pin state to `LOW`
|
||||
inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; }
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
/// Toggle the pin twice to create a short pulse
|
||||
inline void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
|
||||
/// Toggle the pin.
|
||||
/// If the pin was high, set it low. If was low, set it high.
|
||||
inline void toggle() __attribute__ ((always_inline)) { *mInPort = mPinMask; }
|
||||
|
||||
/// Set the same pin on another port to `HIGH`
|
||||
/// @param port the port to modify
|
||||
inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; }
|
||||
/// Set the same pin on another port to `LOW`
|
||||
/// @param port the port to modify
|
||||
inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; }
|
||||
/// Set the state of the output register
|
||||
/// @param val the state to set the output register to
|
||||
/// @note This function is not limited to the current pin! It modifies the entire register.
|
||||
inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *mPort = val; }
|
||||
|
||||
/// Set the state of a port
|
||||
/// @param port the port to modify
|
||||
/// @param val the state to set the port to
|
||||
inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; }
|
||||
|
||||
/// Gets the state of the port with this pin `HIGH`
|
||||
port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask; }
|
||||
/// Gets the state of the port with this pin `LOW`
|
||||
port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; }
|
||||
/// Get the output state of the port
|
||||
port_ptr_t port() __attribute__ ((always_inline)) { return mPort; }
|
||||
/// Get the pin mask
|
||||
port_t mask() __attribute__ ((always_inline)) { return mPinMask; }
|
||||
|
||||
/// @copydoc Pin::hi()
|
||||
virtual void select() override { hi(); }
|
||||
/// @copydoc Pin::lo()
|
||||
virtual void release() override { lo(); }
|
||||
/// Checks if the pin is currently `HIGH`
|
||||
virtual bool isSelected() override { return (*mPort & mPinMask) == mPinMask; }
|
||||
};
|
||||
|
||||
/// I/O pin initially set to OUTPUT
|
||||
class OutputPin : public Pin {
|
||||
public:
|
||||
/// @copydoc Pin::Pin(int)
|
||||
OutputPin(int pin) : Pin(pin) { setOutput(); }
|
||||
};
|
||||
|
||||
/// I/O pin initially set to INPUT
|
||||
class InputPin : public Pin {
|
||||
public:
|
||||
/// @copydoc Pin::Pin(int)
|
||||
InputPin(int pin) : Pin(pin) { setInput(); }
|
||||
};
|
||||
|
||||
#else
|
||||
// This is the empty code version of the raw pin class, method bodies should be filled in to Do The Right Thing[tm] when making this
|
||||
// available on a new platform
|
||||
|
||||
class Pin : public Selectable {
|
||||
volatile RwReg *mPort;
|
||||
volatile RoReg *mInPort;
|
||||
RwReg mPinMask;
|
||||
fl::u8 mPin;
|
||||
|
||||
RwReg mPortFake = 0;
|
||||
RoReg mInPortFake = 0;
|
||||
|
||||
|
||||
|
||||
void _init() {
|
||||
// TODO: fill in init on a new platform
|
||||
mPinMask = 0;
|
||||
mPort = &mPortFake;
|
||||
mInPort = &mInPortFake;
|
||||
}
|
||||
|
||||
public:
|
||||
Pin(int pin) : mPin(pin) { _init(); }
|
||||
#ifndef __AVR__
|
||||
virtual ~Pin() {} // Shut up the compiler warning, but don't steal bytes from AVR.
|
||||
#endif
|
||||
|
||||
void setPin(int pin) { mPin = pin; _init(); }
|
||||
|
||||
typedef volatile RwReg * port_ptr_t;
|
||||
typedef RwReg port_t;
|
||||
|
||||
inline void setOutput() { /* TODO: Set pin output */ }
|
||||
inline void setInput() { /* TODO: Set pin input */ }
|
||||
inline void setInputPullup() { /* TODO: Set pin input pullup */ }
|
||||
|
||||
/// Set the pin state to `HIGH`
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; }
|
||||
/// Set the pin state to `LOW`
|
||||
inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; }
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
inline void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
|
||||
inline void toggle() __attribute__ ((always_inline)) { *mInPort = mPinMask; }
|
||||
|
||||
inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; }
|
||||
inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; }
|
||||
inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *mPort = val; }
|
||||
|
||||
inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; }
|
||||
|
||||
port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask; }
|
||||
port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; }
|
||||
port_ptr_t port() __attribute__ ((always_inline)) { return mPort; }
|
||||
port_t mask() __attribute__ ((always_inline)) { return mPinMask; }
|
||||
|
||||
virtual void select() override { hi(); }
|
||||
virtual void release() override { lo(); }
|
||||
virtual bool isSelected() override { return (*mPort & mPinMask) == mPinMask; }
|
||||
};
|
||||
|
||||
class OutputPin : public Pin {
|
||||
public:
|
||||
OutputPin(int pin) : Pin(pin) { setOutput(); }
|
||||
};
|
||||
|
||||
class InputPin : public Pin {
|
||||
public:
|
||||
InputPin(int pin) : Pin(pin) { setInput(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// The simplest level of Pin class. This relies on runtime functions during initialization to get the port/pin mask for the pin. Most
|
||||
/// of the accesses involve references to these static globals that get set up. This won't be the fastest set of pin operations, but it
|
||||
/// will provide pin level access on pretty much all Arduino environments. In addition, it includes some methods to help optimize access in
|
||||
/// various ways. Namely, the versions of hi(), lo(), and fastset() that take the port register as a passed in register variable (saving a global
|
||||
/// dereference), since these functions are aggressively inlined, that can help collapse out a lot of extraneous memory loads/dereferences.
|
||||
///
|
||||
/// In addition, if, while writing a bunch of data to a pin, you know no other pins will be getting written to, you can get/cache a value of
|
||||
/// the pin's port register and use that to do a full set to the register. This results in one being able to simply do a store to the register,
|
||||
/// vs. the load, and/or, and store that would be done normally.
|
||||
///
|
||||
/// There are platform specific instantiations of this class that provide direct i/o register access to pins for much higher speed pin twiddling.
|
||||
///
|
||||
/// Note that these classes are all static functions. So the proper usage is Pin<13>::hi(); or such. Instantiating objects is not recommended,
|
||||
/// as passing Pin objects around will likely -not- have the effect you're expecting.
|
||||
#ifdef FASTLED_FORCE_SOFTWARE_PINS
|
||||
template<fl::u8 PIN> class FastPin {
|
||||
static RwReg sPinMask; ///< Bitmask for the pin within its register
|
||||
static volatile RwReg *sPort; ///< Output register for the pin
|
||||
static volatile RoReg *sInPort; ///< Input register for the pin
|
||||
static void _init() {
|
||||
#if !defined(FASTLED_NO_PINMAP)
|
||||
sPinMask = digitalPinToBitMask(PIN);
|
||||
sPort = portOutputRegister(digitalPinToPort(PIN));
|
||||
sInPort = portInputRegister(digitalPinToPort(PIN));
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
typedef volatile RwReg * port_ptr_t; ///< @copydoc Pin::port_ptr_t
|
||||
typedef RwReg port_t; ///< @copydoc Pin::port_t
|
||||
|
||||
/// @copydoc Pin::setOutput()
|
||||
inline static void setOutput() { _init(); pinMode(PIN, OUTPUT); }
|
||||
/// @copydoc Pin::setInput()
|
||||
inline static void setInput() { _init(); pinMode(PIN, INPUT); }
|
||||
|
||||
/// @copydoc Pin::hi()
|
||||
inline static void hi() __attribute__ ((always_inline)) { *sPort |= sPinMask; }
|
||||
/// @copydoc Pin::lo()
|
||||
inline static void lo() __attribute__ ((always_inline)) { *sPort &= ~sPinMask; }
|
||||
|
||||
/// @copydoc Pin::strobe()
|
||||
inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
|
||||
|
||||
/// @copydoc Pin::toggle()
|
||||
inline static void toggle() __attribute__ ((always_inline)) { *sInPort = sPinMask; }
|
||||
|
||||
/// @copydoc Pin::hi(FASTLED_REGISTER port_ptr_t)
|
||||
inline static void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= sPinMask; }
|
||||
/// @copydoc Pin::lo(FASTLED_REGISTER port_ptr_t)
|
||||
inline static void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~sPinMask; }
|
||||
/// @copydoc Pin::set(FASTLED_REGISTER port_t)
|
||||
inline static void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *sPort = val; }
|
||||
|
||||
/// @copydoc Pin::fastset()
|
||||
inline static void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; }
|
||||
|
||||
/// @copydoc Pin::hival()
|
||||
static port_t hival() __attribute__ ((always_inline)) { return *sPort | sPinMask; }
|
||||
/// @copydoc Pin::loval()
|
||||
static port_t loval() __attribute__ ((always_inline)) { return *sPort & ~sPinMask; }
|
||||
/// @copydoc Pin::port()
|
||||
static port_ptr_t port() __attribute__ ((always_inline)) { return sPort; }
|
||||
/// @copydoc Pin::mask()
|
||||
static port_t mask() __attribute__ ((always_inline)) { return sPinMask; }
|
||||
};
|
||||
|
||||
template<fl::u8 PIN> RwReg FastPin<PIN>::sPinMask;
|
||||
template<fl::u8 PIN> volatile RwReg *FastPin<PIN>::sPort;
|
||||
template<fl::u8 PIN> volatile RoReg *FastPin<PIN>::sInPort;
|
||||
|
||||
#else
|
||||
|
||||
template<fl::u8 PIN> class FastPin {
|
||||
// This is a default implementation. If you are hitting this then FastPin<> is either:
|
||||
// 1) Not defined -or-
|
||||
// 2) Not part of the set of defined FastPin<> specializations for your platform
|
||||
// You need to define a FastPin<> specialization
|
||||
// or change what get's included for your particular build target.
|
||||
// Keep in mind that these messages are cryptic, so it's best to define an invalid in type.
|
||||
constexpr static bool validpin() { return false; }
|
||||
constexpr static bool LowSpeedOnlyRecommended() { // Some implementations assume this exists.
|
||||
// Caller must always determine if high speed use if allowed on a given pin,
|
||||
// because it depends on more than just the chip packaging ... it depends on entire board (and even system) design.
|
||||
return false; // choosing default to be FALSE, to allow users to ATTEMPT to use high-speed on pins where support is not known
|
||||
}
|
||||
|
||||
static_assert(validpin(), "This pin has been marked as an invalid pin, common reasons includes it being a ground pin, read only, or too noisy (e.g. hooked up to the uart).");
|
||||
|
||||
static void _init() { }
|
||||
|
||||
public:
|
||||
typedef volatile RwReg * port_ptr_t; ///< @copydoc Pin::port_ptr_t
|
||||
typedef RwReg port_t; ///< @copydoc Pin::port_t
|
||||
|
||||
/// @copydoc Pin::setOutput()
|
||||
inline static void setOutput() { }
|
||||
/// @copydoc Pin::setInput()
|
||||
inline static void setInput() { }
|
||||
|
||||
/// @copydoc Pin::hi()
|
||||
inline static void hi() __attribute__ ((always_inline)) { }
|
||||
/// @copydoc Pin::lo()
|
||||
inline static void lo() __attribute__ ((always_inline)) { }
|
||||
|
||||
/// @copydoc Pin::strobe()
|
||||
inline static void strobe() __attribute__ ((always_inline)) { }
|
||||
|
||||
/// @copydoc Pin::toggle()
|
||||
inline static void toggle() __attribute__ ((always_inline)) { }
|
||||
|
||||
/// @copydoc Pin::hi(FASTLED_REGISTER port_ptr_t)
|
||||
inline static void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) {
|
||||
FASTLED_UNUSED(port);
|
||||
}
|
||||
/// @copydoc Pin::lo(FASTLED_REGISTER port_ptr_t)
|
||||
inline static void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) {
|
||||
FASTLED_UNUSED(port);
|
||||
}
|
||||
/// @copydoc Pin::set(FASTLED_REGISTER port_t)
|
||||
inline static void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) {
|
||||
FASTLED_UNUSED(val);
|
||||
}
|
||||
|
||||
/// @copydoc Pin::fastset()
|
||||
inline static void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) {
|
||||
FASTLED_UNUSED(port);
|
||||
FASTLED_UNUSED(val);
|
||||
}
|
||||
|
||||
/// @copydoc Pin::hival()
|
||||
static port_t hival() __attribute__ ((always_inline)) { return 0; }
|
||||
/// @copydoc Pin::loval()
|
||||
static port_t loval() __attribute__ ((always_inline)) { return 0;}
|
||||
/// @copydoc Pin::port()
|
||||
static port_ptr_t port() __attribute__ ((always_inline)) { return NULL; }
|
||||
/// @copydoc Pin::mask()
|
||||
static port_t mask() __attribute__ ((always_inline)) { return 0; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// FastPin implementation for bit-banded access.
|
||||
/// Only for MCUs that support bitbanding.
|
||||
/// @note This bitband class is optional!
|
||||
template<fl::u8 PIN> class FastPinBB : public FastPin<PIN> {};
|
||||
|
||||
typedef volatile fl::u32 & reg32_t; ///< Reference to a 32-bit register, volatile
|
||||
typedef volatile fl::u32 * ptr_reg32_t; ///< Pointer to a 32-bit register, volatile
|
||||
|
||||
/// Utility template for tracking down information about pins and ports
|
||||
/// @tparam port the port to check information for
|
||||
template<fl::u8 port> struct __FL_PORT_INFO {
|
||||
/// Checks whether a port exists
|
||||
static bool hasPort() { return 0; }
|
||||
/// Gets the name of the port, as a C-string
|
||||
static const char *portName() { return "--"; }
|
||||
/// Gets the raw address of the port
|
||||
static const void *portAddr() { return NULL; }
|
||||
};
|
||||
|
||||
|
||||
/// Macro to create the instantiations for defined ports.
|
||||
/// We're going to abuse this later for auto discovery of pin/port mappings
|
||||
/// for new variants.
|
||||
/// Use this for ports that are numeric in nature, e.g. GPIO0, GPIO1, etc.
|
||||
/// @param L the number of the port
|
||||
/// @param BASE the data type for the register
|
||||
#define _FL_DEFINE_PORT(L, BASE) template<> struct __FL_PORT_INFO<L> { \
|
||||
static bool hasPort() { return 1; } \
|
||||
static const char *portName() { return #L; } \
|
||||
typedef BASE __t_baseType; \
|
||||
static const void *portAddr() { return (void*)&__t_baseType::r(); } };
|
||||
|
||||
/// Macro to create the instantiations for defined ports.
|
||||
/// We're going to abuse this later for auto discovery of pin/port mappings
|
||||
/// for new variants.
|
||||
/// Use this for ports that are letters. The first parameter will be the
|
||||
/// letter, the second parameter will be an integer/counter of some kind.
|
||||
/// This is because attempts to turn macro parameters into character constants
|
||||
/// break in some compilers.
|
||||
/// @param L the letter of the port
|
||||
/// @param LC an integer counter
|
||||
/// @param BASE the data type for the register
|
||||
#define _FL_DEFINE_PORT3(L, LC, BASE) template<> struct __FL_PORT_INFO<LC> { \
|
||||
static bool hasPort() { return 1; } \
|
||||
static const char *portName() { return #L; } \
|
||||
typedef BASE __t_baseType; \
|
||||
static const void *portAddr() { return (void*)&__t_baseType::r(); } };
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // __INC_FASTPIN_H
|
||||
199
libraries/FastLED/src/fastspi.h
Normal file
199
libraries/FastLED/src/fastspi.h
Normal file
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi.h
|
||||
/// Serial peripheral interface (SPI) definitions per platform
|
||||
|
||||
#ifndef __INC_FASTSPI_H
|
||||
#define __INC_FASTSPI_H
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "controller.h"
|
||||
#include "lib8tion.h"
|
||||
|
||||
#include "fastspi_bitbang.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
|
||||
|
||||
#if defined(FASTLED_TEENSY3) && (F_CPU > 48000000)
|
||||
#define DATA_RATE_MHZ(X) (((48000000L / 1000000L) / X))
|
||||
#define DATA_RATE_KHZ(X) (((48000000L / 1000L) / X))
|
||||
#elif defined(FASTLED_TEENSY4) || (defined(ESP32) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)) || (defined(ESP8266) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) || defined(FASTLED_STUB_IMPL))
|
||||
// just use clocks
|
||||
#define DATA_RATE_MHZ(X) (1000000 * (X))
|
||||
#define DATA_RATE_KHZ(X) (1000 * (X))
|
||||
#else
|
||||
/// Convert data rate from megahertz (MHz) to clock cycles per bit
|
||||
#define DATA_RATE_MHZ(X) ((F_CPU / 1000000L) / X)
|
||||
/// Convert data rate from kilohertz (KHz) to clock cycles per bit
|
||||
#define DATA_RATE_KHZ(X) ((F_CPU / 1000L) / X)
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// External SPI template definition with partial instantiation(s) to map to hardware SPI ports on platforms/builds where the pin
|
||||
// mappings are known at compile time.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
#if defined(FASTLED_STUB_IMPL)
|
||||
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public fl::StubSPIOutput {};
|
||||
|
||||
|
||||
#else
|
||||
|
||||
#if !defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
/// Hardware SPI output
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public AVRSoftwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
/// Software SPI output
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SoftwareSPIOutput : public AVRSoftwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
|
||||
#ifndef FASTLED_FORCE_SOFTWARE_SPI
|
||||
|
||||
#if defined(NRF51) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public NRF51SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_SERIES) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public NRF52SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
#if defined(FASTLED_APOLLO3) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public APOLLO3HardwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
#if defined(ESP32) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public ESP32SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
template<fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class SPIOutput : public ESP8266SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(SPI_DATA) && defined(SPI_CLOCK)
|
||||
|
||||
#if defined(FASTLED_TEENSY3) && defined(ARM_HARDWARE_SPI)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED, 0x4002C000> {};
|
||||
|
||||
#if defined(SPI2_DATA)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED, 0x4002C000> {};
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI2_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI_DATA, SPI2_CLOCK, SPI_SPEED, 0x4002C000> {};
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI2_DATA, SPI_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI2_DATA, SPI_CLOCK, SPI_SPEED, 0x4002C000> {};
|
||||
#endif
|
||||
|
||||
#elif defined(FASTLED_TEENSY4) && defined(ARM_HARDWARE_SPI)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public Teensy4HardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED, SPI, 0> {};
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI1_DATA, SPI1_CLOCK, SPI_SPEED> : public Teensy4HardwareSPIOutput<SPI1_DATA, SPI1_CLOCK, SPI_SPEED, SPI1, 1> {};
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED> : public Teensy4HardwareSPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED, SPI2, 2> {};
|
||||
|
||||
#elif defined(FASTLED_TEENSYLC) && defined(ARM_HARDWARE_SPI)
|
||||
|
||||
#define DECLARE_SPI0(__DATA,__CLOCK) template<fl::u32 SPI_SPEED>\
|
||||
class SPIOutput<__DATA, __CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<__DATA, __CLOCK, SPI_SPEED, 0x40076000> {};
|
||||
#define DECLARE_SPI1(__DATA,__CLOCK) template<fl::u32 SPI_SPEED>\
|
||||
class SPIOutput<__DATA, __CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<__DATA, __CLOCK, SPI_SPEED, 0x40077000> {};
|
||||
|
||||
DECLARE_SPI0(7,13);
|
||||
DECLARE_SPI0(8,13);
|
||||
DECLARE_SPI0(11,13);
|
||||
DECLARE_SPI0(12,13);
|
||||
DECLARE_SPI0(7,14);
|
||||
DECLARE_SPI0(8,14);
|
||||
DECLARE_SPI0(11,14);
|
||||
DECLARE_SPI0(12,14);
|
||||
DECLARE_SPI1(0,20);
|
||||
DECLARE_SPI1(1,20);
|
||||
DECLARE_SPI1(21,20);
|
||||
|
||||
#elif defined(__SAM3X8E__)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public SAMHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> {};
|
||||
|
||||
#elif defined(AVR_HARDWARE_SPI)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public AVRHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> {};
|
||||
|
||||
#if defined(SPI_UART0_DATA)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_UART0_DATA, SPI_UART0_CLOCK, SPI_SPEED> : public AVRUSART0SPIOutput<SPI_UART0_DATA, SPI_UART0_CLOCK, SPI_SPEED> {};
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(SPI_UART1_DATA)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_UART1_DATA, SPI_UART1_CLOCK, SPI_SPEED> : public AVRUSART1SPIOutput<SPI_UART1_DATA, SPI_UART1_CLOCK, SPI_SPEED> {};
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined(ARDUNIO_CORE_SPI)
|
||||
|
||||
template<fl::u32 SPI_SPEED>
|
||||
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public ArdunioCoreSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED, SPI> {};
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
# if !defined(FASTLED_INTERNAL) && !defined(FASTLED_ALL_PINS_HARDWARE_SPI) && !defined(ESP32)
|
||||
# ifdef FASTLED_HAS_PRAGMA_MESSAGE
|
||||
# pragma message "WARNING: The SPI pins you chose have not been marked as hardware accelerated within the code base. All SPI access will default to bitbanged output. Consult the data sheet for hardware spi pins designed for efficient SPI transfer, typically via DMA / MOSI / SCK / SS pin"
|
||||
# else
|
||||
# warning "The SPI pins you chose have not been marked as hardware accelerated within the code base. All SPI access will default to bitbanged output. Consult the data sheet for hardware spi pins designed for efficient SPI transfer, typically via DMA / MOSI / SCK / SS pins"
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// #if defined(USART_DATA) && defined(USART_CLOCK)
|
||||
// template<uint32_t SPI_SPEED>
|
||||
// class AVRSPIOutput<USART_DATA, USART_CLOCK, SPI_SPEED> : public AVRUSARTSPIOutput<USART_DATA, USART_CLOCK, SPI_SPEED> {};
|
||||
// #endif
|
||||
|
||||
#else
|
||||
# if !defined(FASTLED_INTERNAL) && !defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||
# ifdef FASTLED_HAS_PRAGMA_MESSAGE
|
||||
# pragma message "Forcing software SPI - no hardware accelerated SPI for you!"
|
||||
# else
|
||||
# warning "Forcing software SPI - no hardware accelerated SPI for you!"
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif // !defined(FASTLED_STUB_IMPL)
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
424
libraries/FastLED/src/fastspi_bitbang.h
Normal file
424
libraries/FastLED/src/fastspi_bitbang.h
Normal file
@@ -0,0 +1,424 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi_bitbang.h
|
||||
/// Software SPI (aka bit-banging) support
|
||||
|
||||
#ifndef __INC_FASTSPI_BITBANG_H
|
||||
#define __INC_FASTSPI_BITBANG_H
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "fastled_delay.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Software SPI (aka bit-banging) support
|
||||
/// Includes aggressive optimizations for when the clock and data pin are on the same port.
|
||||
/// @tparam DATA_PIN pin number of the SPI data pin.
|
||||
/// @tparam CLOCK_PIN pin number of the SPI clock pin.
|
||||
/// @tparam SPI_SPEED speed of the bus. Determines the delay times between pin writes.
|
||||
/// @note Although this is named with the "AVR" prefix, this will work on any platform. Theoretically.
|
||||
/// @todo Replace the select pin definition with a set of pins, to allow using mux hardware for routing in the future.
|
||||
template <uint8_t DATA_PIN, uint8_t CLOCK_PIN, fl::u32 SPI_SPEED>
|
||||
class AVRSoftwareSPIOutput {
|
||||
// The data types for pointers to the pin port - typedef'd here from the ::Pin definition because on AVR these
|
||||
// are pointers to 8 bit values, while on ARM they are 32 bit
|
||||
typedef typename FastPin<DATA_PIN>::port_ptr_t data_ptr_t;
|
||||
typedef typename FastPin<CLOCK_PIN>::port_ptr_t clock_ptr_t;
|
||||
|
||||
// The data type for what's at a pin's port - typedef'd here from the Pin definition because on avr the ports
|
||||
// are 8 bits wide while on arm they are 32.
|
||||
typedef typename FastPin<DATA_PIN>::port_t data_t;
|
||||
typedef typename FastPin<CLOCK_PIN>::port_t clock_t;
|
||||
Selectable *m_pSelect; ///< SPI chip select
|
||||
|
||||
public:
|
||||
/// Default constructor
|
||||
AVRSoftwareSPIOutput() { m_pSelect = NULL; }
|
||||
/// Constructor with selectable for SPI chip select
|
||||
AVRSoftwareSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; }
|
||||
|
||||
/// Set the pointer for the SPI chip select
|
||||
/// @param pSelect pointer to chip select control
|
||||
void setSelect(Selectable *pSelect) { m_pSelect = pSelect; }
|
||||
|
||||
/// Set the clock/data pins to output and make sure the chip select is released.
|
||||
void init() {
|
||||
// set the pins to output and make sure the select is released (which apparently means hi? This is a bit
|
||||
// confusing to me)
|
||||
FastPin<DATA_PIN>::setOutput();
|
||||
FastPin<CLOCK_PIN>::setOutput();
|
||||
release();
|
||||
}
|
||||
|
||||
/// Stop the SPI output.
|
||||
/// Pretty much a NOP with software, as there's no registers to kick
|
||||
static void stop() { }
|
||||
|
||||
/// Wait until the SPI subsystem is ready for more data to write.
|
||||
/// A NOP when bitbanging.
|
||||
static void wait() __attribute__((always_inline)) { }
|
||||
/// @copydoc AVRSoftwareSPIOutput::wait()
|
||||
static void waitFully() __attribute__((always_inline)) { wait(); }
|
||||
|
||||
/// Write a single byte over SPI without waiting.
|
||||
static void writeByteNoWait(uint8_t b) __attribute__((always_inline)) { writeByte(b); }
|
||||
/// Write a single byte over SPI and wait afterwards.
|
||||
static void writeBytePostWait(uint8_t b) __attribute__((always_inline)) { writeByte(b); wait(); }
|
||||
|
||||
/// Write a word (two bytes) over SPI.
|
||||
static void writeWord(fl::u16 w) __attribute__((always_inline)) { writeByte(w>>8); writeByte(w&0xFF); }
|
||||
|
||||
/// Write a single byte over SPI.
|
||||
/// Naive implelentation, simply calls writeBit() on the 8 bits in the byte.
|
||||
static void writeByte(uint8_t b) {
|
||||
writeBit<7>(b);
|
||||
writeBit<6>(b);
|
||||
writeBit<5>(b);
|
||||
writeBit<4>(b);
|
||||
writeBit<3>(b);
|
||||
writeBit<2>(b);
|
||||
writeBit<1>(b);
|
||||
writeBit<0>(b);
|
||||
}
|
||||
|
||||
private:
|
||||
/// writeByte() implementation with data/clock registers passed in.
|
||||
static void writeByte(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin) {
|
||||
writeBit<7>(b, clockpin, datapin);
|
||||
writeBit<6>(b, clockpin, datapin);
|
||||
writeBit<5>(b, clockpin, datapin);
|
||||
writeBit<4>(b, clockpin, datapin);
|
||||
writeBit<3>(b, clockpin, datapin);
|
||||
writeBit<2>(b, clockpin, datapin);
|
||||
writeBit<1>(b, clockpin, datapin);
|
||||
writeBit<0>(b, clockpin, datapin);
|
||||
}
|
||||
|
||||
/// writeByte() implementation with the data register passed in and prebaked values for data hi w/clock hi and
|
||||
/// low and data lo w/clock hi and lo. This is to be used when clock and data are on the same GPIO register,
|
||||
/// can get close to getting a bit out the door in 2 clock cycles!
|
||||
static void writeByte(uint8_t b, data_ptr_t datapin,
|
||||
data_t hival, data_t loval,
|
||||
clock_t hiclock, clock_t loclock) {
|
||||
writeBit<7>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<6>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<5>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<4>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<3>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<2>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<1>(b, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<0>(b, datapin, hival, loval, hiclock, loclock);
|
||||
}
|
||||
|
||||
/// writeByte() implementation with not just registers passed in, but pre-baked values for said registers for
|
||||
/// data hi/lo and clock hi/lo values.
|
||||
/// @note Weird things will happen if this method is called in cases where
|
||||
/// the data and clock pins are on the same port! Don't do that!
|
||||
static void writeByte(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin,
|
||||
data_t hival, data_t loval,
|
||||
clock_t hiclock, clock_t loclock) {
|
||||
writeBit<7>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<6>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<5>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<4>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<3>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<2>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<1>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
writeBit<0>(b, clockpin, datapin, hival, loval, hiclock, loclock);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
#if defined(FASTLED_TEENSY4)
|
||||
#define DELAY_NS (1000 / (SPI_SPEED/1000000))
|
||||
#define CLOCK_HI_DELAY do { delayNanoseconds((DELAY_NS/4)); } while(0);
|
||||
#define CLOCK_LO_DELAY do { delayNanoseconds((DELAY_NS/4)); } while(0);
|
||||
#else
|
||||
/// We want to make sure that the clock pulse is held high for a minimum of 35 ns.
|
||||
#define MIN_DELAY ((NS(35)>3) ? (NS(35) - 3) : 1)
|
||||
|
||||
/// Delay for the clock signal 'high' period
|
||||
#define CLOCK_HI_DELAY do { delaycycles<MIN_DELAY>(); delaycycles<((SPI_SPEED > 10) ? (((SPI_SPEED-6) / 2) - MIN_DELAY) : (SPI_SPEED))>(); } while(0);
|
||||
/// Delay for the clock signal 'low' period
|
||||
#define CLOCK_LO_DELAY do { delaycycles<((SPI_SPEED > 10) ? ((SPI_SPEED-6) / 2) : (SPI_SPEED))>(); } while(0);
|
||||
#endif
|
||||
|
||||
/// Write the BIT'th bit out via SPI, setting the data pin then strobing the clock
|
||||
/// @tparam BIT the bit index in the byte
|
||||
/// @param b the byte to read the bit from
|
||||
template <uint8_t BIT> __attribute__((always_inline, hot)) inline static void writeBit(uint8_t b) {
|
||||
//cli();
|
||||
if(b & (1 << BIT)) {
|
||||
FastPin<DATA_PIN>::hi();
|
||||
#ifdef ESP32
|
||||
// try to ensure we never have adjacent write opcodes to the same register
|
||||
FastPin<CLOCK_PIN>::lo();
|
||||
FastPin<CLOCK_PIN>::hi(); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::toggle(); CLOCK_LO_DELAY;
|
||||
#else
|
||||
FastPin<CLOCK_PIN>::hi(); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::lo(); CLOCK_LO_DELAY;
|
||||
#endif
|
||||
} else {
|
||||
FastPin<DATA_PIN>::lo();
|
||||
FastPin<CLOCK_PIN>::hi(); CLOCK_HI_DELAY;
|
||||
#ifdef ESP32
|
||||
// try to ensure we never have adjacent write opcodes to the same register
|
||||
FastPin<CLOCK_PIN>::toggle(); CLOCK_HI_DELAY;
|
||||
#else
|
||||
FastPin<CLOCK_PIN>::lo(); CLOCK_LO_DELAY;
|
||||
#endif
|
||||
}
|
||||
//sei();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Write the BIT'th bit out via SPI, setting the data pin then strobing the clock, using the passed in pin registers to accelerate access if needed
|
||||
template <uint8_t BIT> FASTLED_FORCE_INLINE static void writeBit(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin) {
|
||||
if(b & (1 << BIT)) {
|
||||
FastPin<DATA_PIN>::hi(datapin);
|
||||
FastPin<CLOCK_PIN>::hi(clockpin); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::lo(clockpin); CLOCK_LO_DELAY;
|
||||
} else {
|
||||
FastPin<DATA_PIN>::lo(datapin);
|
||||
FastPin<CLOCK_PIN>::hi(clockpin); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::lo(clockpin); CLOCK_LO_DELAY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// The version of writeBit() to use when clock and data are on separate pins with precomputed values for setting
|
||||
/// the clock and data pins
|
||||
template <uint8_t BIT> FASTLED_FORCE_INLINE static void writeBit(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin,
|
||||
data_t hival, data_t loval, clock_t hiclock, clock_t loclock) {
|
||||
// // only need to explicitly set clock hi if clock and data are on different ports
|
||||
if(b & (1 << BIT)) {
|
||||
FastPin<DATA_PIN>::fastset(datapin, hival);
|
||||
FastPin<CLOCK_PIN>::fastset(clockpin, hiclock); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::fastset(clockpin, loclock); CLOCK_LO_DELAY;
|
||||
} else {
|
||||
// FL_NOP;
|
||||
FastPin<DATA_PIN>::fastset(datapin, loval);
|
||||
FastPin<CLOCK_PIN>::fastset(clockpin, hiclock); CLOCK_HI_DELAY;
|
||||
FastPin<CLOCK_PIN>::fastset(clockpin, loclock); CLOCK_LO_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
/// The version of writeBit() to use when clock and data are on the same port with precomputed values for the various
|
||||
/// combinations
|
||||
template <uint8_t BIT> FASTLED_FORCE_INLINE static void writeBit(uint8_t b, data_ptr_t clockdatapin,
|
||||
data_t datahiclockhi, data_t dataloclockhi,
|
||||
data_t datahiclocklo, data_t dataloclocklo) {
|
||||
#if 0
|
||||
writeBit<BIT>(b);
|
||||
#else
|
||||
if(b & (1 << BIT)) {
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, datahiclocklo);
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, datahiclockhi); CLOCK_HI_DELAY;
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, datahiclocklo); CLOCK_LO_DELAY;
|
||||
} else {
|
||||
// FL_NOP;
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, dataloclocklo);
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, dataloclockhi); CLOCK_HI_DELAY;
|
||||
FastPin<DATA_PIN>::fastset(clockdatapin, dataloclocklo); CLOCK_LO_DELAY;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Select the SPI output (chip select)
|
||||
/// @todo Research whether this really means 'hi' or 'lo'.
|
||||
/// @par
|
||||
/// @todo Move select responsibility out of the SPI classes entirely,
|
||||
/// make it up to the caller to remember to lock/select the line?
|
||||
void select() { if(m_pSelect != NULL) { m_pSelect->select(); } } // FastPin<SELECT_PIN>::hi(); }
|
||||
|
||||
/// Release the SPI chip select line
|
||||
void release() { if(m_pSelect != NULL) { m_pSelect->release(); } } // FastPin<SELECT_PIN>::lo(); }
|
||||
|
||||
/// Write multiple bytes of the given value over SPI.
|
||||
/// Useful for quickly flushing, say, a line of 0's down the line.
|
||||
/// @param value the value to write to the bus
|
||||
/// @param len how many copies of the value to write
|
||||
void writeBytesValue(uint8_t value, int len) {
|
||||
select();
|
||||
writeBytesValueRaw(value, len);
|
||||
release();
|
||||
}
|
||||
|
||||
/// Write multiple bytes of the given value over SPI, without selecting the interface.
|
||||
/// @copydetails AVRSoftwareSPIOutput::writeBytesValue(uint8_t, int)
|
||||
static void writeBytesValueRaw(uint8_t value, int len) {
|
||||
#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS
|
||||
// TODO: Weird things may happen if software bitbanging SPI output and other pins on the output reigsters are being twiddled. Need
|
||||
// to allow specifying whether or not exclusive i/o access is allowed during this process, and if i/o access is not allowed fall
|
||||
// back to the degenerative code below
|
||||
while(len--) {
|
||||
writeByte(value);
|
||||
}
|
||||
#else
|
||||
FASTLED_REGISTER data_ptr_t datapin = FastPin<DATA_PIN>::port();
|
||||
|
||||
if(FastPin<DATA_PIN>::port() != FastPin<CLOCK_PIN>::port()) {
|
||||
// If data and clock are on different ports, then writing a bit will consist of writing the value foor
|
||||
// the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line
|
||||
FASTLED_REGISTER clock_ptr_t clockpin = FastPin<CLOCK_PIN>::port();
|
||||
FASTLED_REGISTER data_t datahi = FastPin<DATA_PIN>::hival();
|
||||
FASTLED_REGISTER data_t datalo = FastPin<DATA_PIN>::loval();
|
||||
FASTLED_REGISTER clock_t clockhi = FastPin<CLOCK_PIN>::hival();
|
||||
FASTLED_REGISTER clock_t clocklo = FastPin<CLOCK_PIN>::loval();
|
||||
while(len--) {
|
||||
writeByte(value, clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
}
|
||||
|
||||
} else {
|
||||
// If data and clock are on the same port then we can combine setting the data and clock pins
|
||||
FASTLED_REGISTER data_t datahi_clockhi = FastPin<DATA_PIN>::hival() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clockhi = FastPin<DATA_PIN>::loval() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datahi_clocklo = FastPin<DATA_PIN>::hival() & ~FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clocklo = FastPin<DATA_PIN>::loval() & ~FastPin<CLOCK_PIN>::mask();
|
||||
|
||||
while(len--) {
|
||||
writeByte(value, datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Write an array of data to the SPI interface.
|
||||
/// @tparam D Per-byte modifier class, e.g. ::DATA_NOP
|
||||
/// @param data pointer to data to write
|
||||
/// @param len number of bytes to write
|
||||
/// @todo Need to type this better so that explicit casts into the call aren't required.
|
||||
template <class D> void writeBytes(FASTLED_REGISTER uint8_t *data, int len) {
|
||||
select();
|
||||
#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS
|
||||
uint8_t *end = data + len;
|
||||
while(data != end) {
|
||||
writeByte(D::adjust(*data++));
|
||||
}
|
||||
#else
|
||||
FASTLED_REGISTER clock_ptr_t clockpin = FastPin<CLOCK_PIN>::port();
|
||||
FASTLED_REGISTER data_ptr_t datapin = FastPin<DATA_PIN>::port();
|
||||
|
||||
if(FastPin<DATA_PIN>::port() != FastPin<CLOCK_PIN>::port()) {
|
||||
// If data and clock are on different ports, then writing a bit will consist of writing the value foor
|
||||
// the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line
|
||||
FASTLED_REGISTER data_t datahi = FastPin<DATA_PIN>::hival();
|
||||
FASTLED_REGISTER data_t datalo = FastPin<DATA_PIN>::loval();
|
||||
FASTLED_REGISTER clock_t clockhi = FastPin<CLOCK_PIN>::hival();
|
||||
FASTLED_REGISTER clock_t clocklo = FastPin<CLOCK_PIN>::loval();
|
||||
uint8_t *end = data + len;
|
||||
|
||||
while(data != end) {
|
||||
writeByte(D::adjust(*data++), clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
}
|
||||
|
||||
} else {
|
||||
// FastPin<CLOCK_PIN>::hi();
|
||||
// If data and clock are on the same port then we can combine setting the data and clock pins
|
||||
FASTLED_REGISTER data_t datahi_clockhi = FastPin<DATA_PIN>::hival() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clockhi = FastPin<DATA_PIN>::loval() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datahi_clocklo = FastPin<DATA_PIN>::hival() & ~FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clocklo = FastPin<DATA_PIN>::loval() & ~FastPin<CLOCK_PIN>::mask();
|
||||
|
||||
uint8_t *end = data + len;
|
||||
|
||||
while(data != end) {
|
||||
writeByte(D::adjust(*data++), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
}
|
||||
// FastPin<CLOCK_PIN>::lo();
|
||||
}
|
||||
#endif
|
||||
D::postBlock(len);
|
||||
release();
|
||||
}
|
||||
|
||||
/// Write an array of data to the SPI interface.
|
||||
/// @param data pointer to data to write
|
||||
/// @param len number of bytes to write
|
||||
void writeBytes(FASTLED_REGISTER uint8_t *data, int len) { writeBytes<DATA_NOP>(data, len); }
|
||||
|
||||
|
||||
/// Write LED pixel data to the SPI interface.
|
||||
/// Data is written in groups of three, re-ordered per the RGB_ORDER.
|
||||
/// @tparam FLAGS Option flags, such as ::FLAG_START_BIT
|
||||
/// @tparam D Per-byte modifier class, e.g. ::DATA_NOP
|
||||
/// @tparam RGB_ORDER the rgb ordering for the LED data (e.g. what order red, green, and blue data is written out in)
|
||||
/// @param pixels a ::PixelController with the LED data and modifier options
|
||||
template <uint8_t FLAGS, class D, EOrder RGB_ORDER> __attribute__((noinline)) void writePixels(PixelController<RGB_ORDER> pixels, void* context = NULL) {
|
||||
FASTLED_UNUSED(context);
|
||||
select();
|
||||
int len = pixels.mLen;
|
||||
|
||||
#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS
|
||||
// If interrupts or other things may be generating output while we're working on things, then we need
|
||||
// to use this block
|
||||
while(pixels.has(1)) {
|
||||
if(FLAGS & FLAG_START_BIT) {
|
||||
writeBit<0>(1);
|
||||
}
|
||||
writeByte(D::adjust(pixels.loadAndScale0()));
|
||||
writeByte(D::adjust(pixels.loadAndScale1()));
|
||||
writeByte(D::adjust(pixels.loadAndScale2()));
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
#else
|
||||
// If we can guaruntee that no one else will be writing data while we are running (namely, changing the values of the PORT/PDOR pins)
|
||||
// then we can use a bunch of optimizations in here
|
||||
FASTLED_REGISTER data_ptr_t datapin = FastPin<DATA_PIN>::port();
|
||||
|
||||
if(FastPin<DATA_PIN>::port() != FastPin<CLOCK_PIN>::port()) {
|
||||
FASTLED_REGISTER clock_ptr_t clockpin = FastPin<CLOCK_PIN>::port();
|
||||
// If data and clock are on different ports, then writing a bit will consist of writing the value foor
|
||||
// the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line
|
||||
FASTLED_REGISTER data_t datahi = FastPin<DATA_PIN>::hival();
|
||||
FASTLED_REGISTER data_t datalo = FastPin<DATA_PIN>::loval();
|
||||
FASTLED_REGISTER clock_t clockhi = FastPin<CLOCK_PIN>::hival();
|
||||
FASTLED_REGISTER clock_t clocklo = FastPin<CLOCK_PIN>::loval();
|
||||
|
||||
while(pixels.has(1)) {
|
||||
if(FLAGS & FLAG_START_BIT) {
|
||||
writeBit<0>(1, clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
}
|
||||
writeByte(D::adjust(pixels.loadAndScale0()), clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
writeByte(D::adjust(pixels.loadAndScale1()), clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
writeByte(D::adjust(pixels.loadAndScale2()), clockpin, datapin, datahi, datalo, clockhi, clocklo);
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
|
||||
} else {
|
||||
// If data and clock are on the same port then we can combine setting the data and clock pins
|
||||
FASTLED_REGISTER data_t datahi_clockhi = FastPin<DATA_PIN>::hival() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clockhi = FastPin<DATA_PIN>::loval() | FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datahi_clocklo = FastPin<DATA_PIN>::hival() & ~FastPin<CLOCK_PIN>::mask();
|
||||
FASTLED_REGISTER data_t datalo_clocklo = FastPin<DATA_PIN>::loval() & ~FastPin<CLOCK_PIN>::mask();
|
||||
|
||||
while(pixels.has(1)) {
|
||||
if(FLAGS & FLAG_START_BIT) {
|
||||
writeBit<0>(1, datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
}
|
||||
writeByte(D::adjust(pixels.loadAndScale0()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
writeByte(D::adjust(pixels.loadAndScale1()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
writeByte(D::adjust(pixels.loadAndScale2()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo);
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
D::postBlock(len);
|
||||
release();
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
5
libraries/FastLED/src/fastspi_dma.h
Normal file
5
libraries/FastLED/src/fastspi_dma.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi_dma.h
|
||||
/// Direct memory access (DMA) functions for SPI interfaces
|
||||
/// @deprecated This header file is empty.
|
||||
74
libraries/FastLED/src/fastspi_nop.h
Normal file
74
libraries/FastLED/src/fastspi_nop.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi_nop.h
|
||||
/// Example of a NOP/stub class to show the SPI methods required by a chipset implementation
|
||||
/// @note Example for developers. Not a functional part of the library.
|
||||
|
||||
#ifndef __INC_FASTSPI_NOP_H
|
||||
#define __INC_FASTSPI_NOP_H
|
||||
|
||||
#if FASTLED_DOXYGEN // Guard against the arduino ide idiotically including every header file
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// A nop/stub class, mostly to show the SPI methods that are needed/used by the various SPI chipset implementations. Should
|
||||
/// be used as a definition for the set of methods that the spi implementation classes should use (since C++ doesn't support the
|
||||
/// idea of interfaces - it's possible this could be done with virtual classes, need to decide if i want that overhead)
|
||||
template <fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, uint32_t _SPI_CLOCK_DIVIDER>
|
||||
class NOPSPIOutput {
|
||||
Selectable *m_pSelect;
|
||||
|
||||
public:
|
||||
/// Default Constructor
|
||||
NOPSPIOutput() { m_pSelect = NULL; }
|
||||
|
||||
/// Constructor with selectable
|
||||
NOPSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; }
|
||||
|
||||
/// set the object representing the selectable
|
||||
void setSelect(Selectable *pSelect) { m_pSelect = pSelect; }
|
||||
|
||||
/// initialize the SPI subssytem
|
||||
void init() { /* TODO */ }
|
||||
|
||||
/// latch the CS select
|
||||
void select() { /* TODO */ }
|
||||
|
||||
/// release the CS select
|
||||
void release() { /* TODO */ }
|
||||
|
||||
/// wait until all queued up data has been written
|
||||
void waitFully();
|
||||
|
||||
/// not the most efficient mechanism in the world - but should be enough for sm16716 and friends
|
||||
template <fl::u8 BIT> inline static void writeBit(fl::u8 b) { /* TODO */ }
|
||||
|
||||
/// write a byte out via SPI (returns immediately on writing register)
|
||||
void writeByte(fl::u8 b) { /* TODO */ }
|
||||
/// write a word out via SPI (returns immediately on writing register)
|
||||
void writeWord(uint16_t w) { /* TODO */ }
|
||||
|
||||
/// A raw set of writing byte values, assumes setup/init/waiting done elsewhere (static for use by adjustment classes)
|
||||
static void writeBytesValueRaw(fl::u8 value, int len) { /* TODO */ }
|
||||
|
||||
/// A full cycle of writing a value for len bytes, including select, release, and waiting
|
||||
void writeBytesValue(fl::u8 value, int len) { /* TODO */ }
|
||||
|
||||
/// A full cycle of writing a raw block of data out, including select, release, and waiting
|
||||
void writeBytes(fl::u8 *data, int len) { /* TODO */ }
|
||||
|
||||
/// write a single bit out, which bit from the passed in byte is determined by template parameter
|
||||
template <fl::u8 BIT> inline static void writeBit(fl::u8 b) { /* TODO */ }
|
||||
|
||||
/// write out pixel data from the given PixelController object
|
||||
template <fl::u8 FLAGS, class D, EOrder RGB_ORDER> void writePixels(PixelController<RGB_ORDER> pixels, void* context = NULL) { /* TODO */ }
|
||||
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
#endif
|
||||
105
libraries/FastLED/src/fastspi_ref.h
Normal file
105
libraries/FastLED/src/fastspi_ref.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi_ref.h
|
||||
/// Example of a hardware SPI support class.
|
||||
/// @note Example for developers. Not a functional part of the library.
|
||||
|
||||
#ifndef __INC_FASTSPI_ARM_SAM_H
|
||||
#define __INC_FASTSPI_ARM_SAM_H
|
||||
|
||||
#if FASTLED_DOXYGEN // guard against the arduino ide idiotically including every header file
|
||||
#include "FastLED.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// A skeletal implementation of hardware SPI support. Fill in the necessary code for init, waiting, and writing. The rest of
|
||||
/// the method implementations should provide a starting point, even if they're not the most efficient to start with
|
||||
template <fl::u8 _DATA_PIN, fl::u8 _CLOCK_PIN, fl::u32 _SPI_CLOCK_DIVIDER>
|
||||
class REFHardwareSPIOutput {
|
||||
Selectable *m_pSelect;
|
||||
|
||||
public:
|
||||
/// Default Constructor
|
||||
SAMHardwareSPIOutput() { m_pSelect = NULL; }
|
||||
|
||||
/// Constructor with selectable
|
||||
SAMHArdwareSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; }
|
||||
|
||||
/// set the object representing the selectable
|
||||
void setSelect(Selectable *pSelect) { /* TODO */ }
|
||||
|
||||
/// initialize the SPI subssytem
|
||||
void init() { /* TODO */ }
|
||||
|
||||
/// latch the CS select
|
||||
void inline select() __attribute__((always_inline)) { if(m_pSelect != NULL) { m_pSelect->select(); } }
|
||||
|
||||
/// release the CS select
|
||||
void inline release() __attribute__((always_inline)) { if(m_pSelect != NULL) { m_pSelect->release(); } }
|
||||
|
||||
/// wait until all queued up data has been written
|
||||
static void waitFully() { /* TODO */ }
|
||||
|
||||
/// write a byte out via SPI (returns immediately on writing register)
|
||||
static void writeByte(fl::u8 b) { /* TODO */ }
|
||||
|
||||
/// write a word out via SPI (returns immediately on writing register)
|
||||
static void writeWord(uint16_t w) { /* TODO */ }
|
||||
|
||||
/// A raw set of writing byte values, assumes setup/init/waiting done elsewhere
|
||||
static void writeBytesValueRaw(fl::u8 value, int len) {
|
||||
while(len--) { writeByte(value); }
|
||||
}
|
||||
|
||||
/// A full cycle of writing a value for len bytes, including select, release, and waiting
|
||||
void writeBytesValue(fl::u8 value, int len) {
|
||||
select(); writeBytesValueRaw(value, len); release();
|
||||
}
|
||||
|
||||
/// A full cycle of writing a value for len bytes, including select, release, and waiting
|
||||
template <class D> void writeBytes(FASTLED_REGISTER fl::u8 *data, int len) {
|
||||
fl::u8 *end = data + len;
|
||||
select();
|
||||
// could be optimized to write 16bit words out instead of 8bit bytes
|
||||
while(data != end) {
|
||||
writeByte(D::adjust(*data++));
|
||||
}
|
||||
D::postBlock(len);
|
||||
waitFully();
|
||||
release();
|
||||
}
|
||||
|
||||
/// A full cycle of writing a value for len bytes, including select, release, and waiting
|
||||
void writeBytes(FASTLED_REGISTER fl::u8 *data, int len) { writeBytes<DATA_NOP>(data, len); }
|
||||
|
||||
/// write a single bit out, which bit from the passed in byte is determined by template parameter
|
||||
template <fl::u8 BIT> inline static void writeBit(fl::u8 b) { /* TODO */ }
|
||||
|
||||
/// write a block of uint8_ts out in groups of three. len is the total number of uint8_ts to write out. The template
|
||||
/// parameters indicate how many uint8_ts to skip at the beginning and/or end of each grouping
|
||||
template <fl::u8 FLAGS, class D, EOrder RGB_ORDER> void writePixels(PixelController<RGB_ORDER> pixels, void* context = NULL) {
|
||||
select();
|
||||
while(data != end) {
|
||||
if(FLAGS & FLAG_START_BIT) {
|
||||
writeBit<0>(1);
|
||||
}
|
||||
writeByte(D::adjust(pixels.loadAndScale0()));
|
||||
writeByte(D::adjust(pixels.loadAndScale1()));
|
||||
writeByte(D::adjust(pixels.loadAndScale2()));
|
||||
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
data += (3+skip);
|
||||
}
|
||||
D::postBlock(len);
|
||||
release();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
89
libraries/FastLED/src/fastspi_types.h
Normal file
89
libraries/FastLED/src/fastspi_types.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fastspi_types.h
|
||||
/// Data types and constants used by SPI interfaces
|
||||
|
||||
#ifndef __INC_FASTSPI_TYPES_H
|
||||
#define __INC_FASTSPI_TYPES_H
|
||||
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/unused.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
/// @name Byte Re-Order Macros
|
||||
/// Some helper macros for getting at mis-ordered byte values.
|
||||
/// @todo Unused. Remove?
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Get SPI byte 0 offset
|
||||
#define SPI_B0 (RGB_BYTE0(RGB_ORDER) + (MASK_SKIP_BITS & SKIP))
|
||||
/// Get SPI byte 1 offset
|
||||
#define SPI_B1 (RGB_BYTE1(RGB_ORDER) + (MASK_SKIP_BITS & SKIP))
|
||||
/// Get SPI byte 2 offset
|
||||
#define SPI_B2 (RGB_BYTE2(RGB_ORDER) + (MASK_SKIP_BITS & SKIP))
|
||||
/// Advance SPI data pointer
|
||||
#define SPI_ADVANCE (3 + (MASK_SKIP_BITS & SKIP))
|
||||
/// @}
|
||||
|
||||
/// Dummy class for output controllers that need no data transformations.
|
||||
/// Some of the SPI controllers will need to perform a transform on each byte before doing
|
||||
/// anything with it. Creating a class of this form and passing it in as a template parameter to
|
||||
/// writeBytes()/writeBytes3() will ensure that the body of this method will get called on every
|
||||
/// byte worked on.
|
||||
/// @note Recommendation: make the adjust method aggressively inlined.
|
||||
/// @todo Convinience macro for building these
|
||||
class DATA_NOP {
|
||||
public:
|
||||
/// Hook called to adjust a byte of data before writing it to the output.
|
||||
/// In this dummy version, no adjustment is made.
|
||||
static FASTLED_FORCE_INLINE uint8_t adjust(FASTLED_REGISTER uint8_t data) { return data; }
|
||||
|
||||
/// @copybrief adjust(FASTLED_REGISTER uint8_t)
|
||||
/// @param data input byte
|
||||
/// @param scale scale value
|
||||
/// @returns input byte rescaled using ::scale8(uint8_t, uint8_t)
|
||||
static FASTLED_FORCE_INLINE uint8_t adjust(FASTLED_REGISTER uint8_t data, FASTLED_REGISTER uint8_t scale) { return scale8(data, scale); }
|
||||
|
||||
/// Hook called after a block of data is written to the output.
|
||||
/// In this dummy version, no action is performed.
|
||||
static FASTLED_FORCE_INLINE void postBlock(int /* len */, void* context = NULL) {
|
||||
FASTLED_UNUSED(context);
|
||||
}
|
||||
};
|
||||
|
||||
/// Flag for the start of an SPI transaction
|
||||
#define FLAG_START_BIT 0x80
|
||||
|
||||
/// Bitmask for the lower 6 bits of a byte
|
||||
/// @todo Unused. Remove?
|
||||
#define MASK_SKIP_BITS 0x3F
|
||||
|
||||
/// @name Clock speed dividers
|
||||
/// @{
|
||||
|
||||
/// Divisor for clock speed by 2
|
||||
#define SPEED_DIV_2 2
|
||||
/// Divisor for clock speed by 4
|
||||
#define SPEED_DIV_4 4
|
||||
/// Divisor for clock speed by 8
|
||||
#define SPEED_DIV_8 8
|
||||
/// Divisor for clock speed by 16
|
||||
#define SPEED_DIV_16 16
|
||||
/// Divisor for clock speed by 32
|
||||
#define SPEED_DIV_32 32
|
||||
/// Divisor for clock speed by 64
|
||||
#define SPEED_DIV_64 64
|
||||
/// Divisor for clock speed by 128
|
||||
#define SPEED_DIV_128 128
|
||||
/// @}
|
||||
|
||||
/// Max SPI data rate
|
||||
/// @todo Unused. Remove?
|
||||
#define MAX_DATA_RATE 0
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
798
libraries/FastLED/src/fl/README.md
Normal file
798
libraries/FastLED/src/fl/README.md
Normal file
@@ -0,0 +1,798 @@
|
||||
# FastLED Core Library (`src/fl`)
|
||||
|
||||
This document introduces the FastLED core library housed under `src/fl/`, first by listing its headers, then by progressively expanding into an educational guide for two audiences:
|
||||
- First‑time FastLED users who want to understand what lives below `FastLED.h` and how to use common utilities
|
||||
- Experienced C++ developers exploring the `fl::` API as a cross‑platform, STL‑free foundation
|
||||
|
||||
FastLED avoids direct dependencies on the C++ standard library in embedded contexts and offers its own STL‑like building blocks in the `fl::` namespace.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview and Quick Start](#overview-and-quick-start)
|
||||
- [Header Groups (5 major areas)](#header-groups-5-major-areas)
|
||||
- [STL‑like data structures and core utilities](#1-stl-like-data-structures-and-core-utilities)
|
||||
- [Graphics, geometry, and rendering](#2-graphics-geometry-and-rendering)
|
||||
- [Color, math, and signal processing](#3-color-math-and-signal-processing)
|
||||
- [Concurrency, async, and functional](#4-concurrency-async-and-functional)
|
||||
- [I/O, JSON, and text/formatting](#5-io-json-and-textformatting)
|
||||
- [Comprehensive Module Breakdown](#comprehensive-module-breakdown)
|
||||
- [Guidance for New Users](#guidance-for-new-users)
|
||||
- [Guidance for C++ Developers](#guidance-for-c-developers)
|
||||
|
||||
---
|
||||
|
||||
## Overview and Quick Start
|
||||
|
||||
### What is `fl::`?
|
||||
|
||||
`fl::` is FastLED’s cross‑platform foundation layer. It provides containers, algorithms, memory utilities, math, graphics primitives, async/concurrency, I/O, and platform shims designed to work consistently across embedded targets and host builds. It replaces common `std::` facilities with equivalents tailored for embedded constraints and portability.
|
||||
|
||||
Key properties:
|
||||
- Cross‑platform, embedded‑friendly primitives
|
||||
- Minimal dynamic allocation where possible; clear ownership semantics
|
||||
- Consistent naming and behavior across compilers/toolchains
|
||||
- Prefer composable, header‑driven utilities
|
||||
|
||||
### Goals and design constraints
|
||||
|
||||
- Avoid fragile dependencies on `std::` in embedded builds; prefer `fl::` types
|
||||
- Emphasize deterministic behavior and low overhead
|
||||
- Provide familiar concepts (vector, span, optional, variant, function) with embedded‑aware implementations
|
||||
- Offer safe RAII ownership types and moveable wrappers for resource management
|
||||
- Keep APIs flexible by preferring non‑owning views (`fl::span`) as function parameters
|
||||
|
||||
### Naming and idioms
|
||||
|
||||
- Names live in the `fl::` namespace
|
||||
- Prefer `fl::span<T>` as input parameters over owning containers
|
||||
- Use `fl::shared_ptr<T>` / `fl::unique_ptr<T>` instead of raw pointers
|
||||
- Favor explicit ownership and lifetimes; avoid manual `new`/`delete`
|
||||
|
||||
### Getting started: common building blocks
|
||||
|
||||
Include the top‑level header you need, or just include `FastLED.h` in sketches. When writing platform‑agnostic C++ within this repo, include the specific `fl/` headers you use.
|
||||
|
||||
Common types to reach for:
|
||||
- Containers and views: `fl::vector<T>`, `fl::deque<T>`, `fl::span<T>`, `fl::slice<T>`
|
||||
- Strings and streams: `fl::string`, `fl::ostream`, `fl::sstream`, `fl::printf`
|
||||
- Optionals and variants: `fl::optional<T>`, `fl::variant<...>`
|
||||
- Memory/ownership: `fl::unique_ptr<T>`, `fl::shared_ptr<T>`, `fl::weak_ptr<T>`
|
||||
- Functional: `fl::function<Signature>`, `fl::function_list<Signature>`
|
||||
- Concurrency: `fl::thread`, `fl::mutex`, `fl::thread_local`
|
||||
- Async: `fl::promise<T>`, `fl::task`
|
||||
- Math: `fl::math`, `fl::sin32`, `fl::random`, `fl::gamma`, `fl::gradient`
|
||||
- Graphics: `fl::raster`, `fl::screenmap`, `fl::rectangular_draw_buffer`, `fl::downscale`, `fl::supersample`
|
||||
- Color: `fl::hsv`, `fl::hsv16`, `fl::colorutils`
|
||||
- JSON: `fl::Json` with safe defaults and ergonomic access
|
||||
|
||||
Example: using containers, views, and ownership
|
||||
|
||||
```cpp
|
||||
#include "fl/vector.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/memory.h" // for fl::make_unique
|
||||
|
||||
void process(fl::span<const int> values) {
|
||||
// Non-owning view over contiguous data
|
||||
}
|
||||
|
||||
void example() {
|
||||
fl::vector<int> data;
|
||||
data.push_back(1);
|
||||
data.push_back(2);
|
||||
data.push_back(3);
|
||||
|
||||
process(fl::span<const int>(data));
|
||||
|
||||
auto ptr = fl::make_unique<int>(42);
|
||||
// Exclusive ownership; automatically freed when leaving scope
|
||||
}
|
||||
```
|
||||
|
||||
Example: JSON with safe default access
|
||||
|
||||
```cpp
|
||||
#include "fl/json.h"
|
||||
|
||||
void json_example(const fl::string& jsonStr) {
|
||||
fl::Json json = fl::Json::parse(jsonStr);
|
||||
int brightness = json["config"]["brightness"] | 128; // default if missing
|
||||
bool enabled = json["enabled"] | false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Header Groups (5 major areas)
|
||||
|
||||
### 1) STL‑like data structures and core utilities
|
||||
|
||||
Containers, views, algorithms, compile‑time utilities, memory/ownership, portability helpers.
|
||||
|
||||
- Containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h`, `bitset.h`, `bitset_dynamic.h`
|
||||
- Views and ranges: `span.h`, `slice.h`, `range_access.h`
|
||||
- Tuples and algebraic types: `tuple.h`, `pair.h`, `optional.h`, `variant.h`
|
||||
- Algorithms and helpers: `algorithm.h`, `transform.h`, `comparators.h`, `range_access.h`
|
||||
- Types and traits: `types.h`, `type_traits.h`, `initializer_list.h`, `utility.h`, `move.h`, `template_magic.h`, `stdint.h`, `cstddef.h`, `namespace.h`
|
||||
- Memory/ownership: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `ptr.h`, `ptr_impl.h`, `referent.h`, `allocator.h`, `memory.h`, `memfill.h`, `inplacenew.h`
|
||||
- Portability and compiler control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h`, `deprecated.h`, `avr_disallowed.h`, `bit_cast.h`, `id_tracker.h`, `insert_result.h`, `singleton.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `vector.h`: Dynamically sized contiguous container with embedded‑friendly API.
|
||||
- `deque.h`: Double‑ended queue for efficient front/back operations.
|
||||
- `queue.h`: FIFO adapter providing push/pop semantics over an underlying container.
|
||||
- `priority_queue.h`: Heap‑based ordered queue for highest‑priority retrieval.
|
||||
- `set.h`: Ordered unique collection with deterministic iteration.
|
||||
- `map.h`: Ordered key‑value associative container.
|
||||
- `unordered_set.h`: Hash‑based unique set for average O(1) lookups.
|
||||
- `hash_map.h`: Hash‑based key‑value container tuned for embedded use.
|
||||
- `hash_set.h`: Hash set implementation complementing `hash_map.h`.
|
||||
- `rbtree.h`: Balanced tree primitive used by ordered containers.
|
||||
- `bitset.h`: Fixed‑size compile‑time bitset operations.
|
||||
- `bitset_dynamic.h`: Runtime‑sized bitset for flexible masks.
|
||||
- `span.h`: Non‑owning view over contiguous memory (preferred function parameter).
|
||||
- `slice.h`: Strided or sub‑range view utilities for buffers.
|
||||
- `range_access.h`: Helpers to unify begin/end access over custom ranges.
|
||||
- `tuple.h`: Heterogeneous fixed‑size aggregate with structured access.
|
||||
- `pair.h`: Two‑value aggregate type for simple key/value or coordinate pairs.
|
||||
- `optional.h`: Presence/absence wrapper to avoid sentinel values.
|
||||
- `variant.h`: Type‑safe tagged union for sum types without heap allocation.
|
||||
- `algorithm.h`: Core algorithms (search, sort helpers, transforms) adapted to `fl::` containers.
|
||||
- `transform.h`: Functional style element‑wise transformations with spans/ranges.
|
||||
- `comparators.h`: Reusable comparator utilities for ordering operations.
|
||||
- `types.h`: Canonical type aliases and shared type definitions.
|
||||
- `type_traits.h`: Compile‑time type inspection and enable_if‑style utilities.
|
||||
- `initializer_list.h`: Lightweight initializer list support for container construction.
|
||||
- `utility.h`: Miscellaneous helpers (swap, forward, etc.) suitable for embedded builds.
|
||||
- `move.h`: Move/forward utilities mirroring standard semantics.
|
||||
- `template_magic.h`: Metaprogramming helpers to simplify template code.
|
||||
- `stdint.h`: Fixed‑width integer definitions for cross‑compiler consistency.
|
||||
- `cstddef.h`: Size/ptrdiff and nullptr utilities for portability.
|
||||
- `namespace.h`: Internal macros/utilities for managing `fl::` namespaces safely.
|
||||
- `unique_ptr.h`: Exclusive ownership smart pointer with RAII semantics.
|
||||
- `shared_ptr.h`: Reference‑counted shared ownership smart pointer.
|
||||
- `weak_ptr.h`: Non‑owning reference to `shared_ptr`‑managed objects.
|
||||
- `scoped_ptr.h`: Scope‑bound ownership (no move) for simple RAII cleanup.
|
||||
- `scoped_array.h`: RAII wrapper for array allocations.
|
||||
- `ptr.h`/`ptr_impl.h`: Pointer abstractions and shared machinery for smart pointers.
|
||||
- `referent.h`: Base support for referent/observer relationships.
|
||||
- `allocator.h`: Custom allocators tailored for embedded constraints.
|
||||
- `memory.h`: Low‑level memory helpers (construct/destroy, address utilities).
|
||||
- `memfill.h`: Zero‑cost fill utilities (prefer over `memset` in codebase).
|
||||
- `inplacenew.h`: Placement new helpers for manual lifetime management.
|
||||
- `compiler_control.h`: Unified compiler warning/pragma control macros.
|
||||
- `force_inline.h`: Portable always‑inline control macros.
|
||||
- `virtual_if_not_avr.h`: Virtual specifier abstraction for AVR compatibility.
|
||||
- `has_define.h`: Preprocessor feature checks and conditional compilation helpers.
|
||||
- `register.h`: Register annotation shims for portability.
|
||||
- `warn.h`/`trace.h`/`dbg.h`: Logging, tracing, and diagnostics helpers.
|
||||
- `assert.h`: Assertions suited for embedded/testing builds.
|
||||
- `unused.h`: Intentional unused variable/function annotations.
|
||||
- `export.h`/`dll.h`: Visibility/export macros for shared library boundaries.
|
||||
- `deprecated.h`: Cross‑compiler deprecation annotations.
|
||||
- `avr_disallowed.h`: Guardrails to prevent unsupported usage on AVR.
|
||||
- `bit_cast.h`: Safe bit reinterpretation where supported, with fallbacks.
|
||||
- `id_tracker.h`: ID generation/tracking utility for object registries.
|
||||
- `insert_result.h`: Standardized result type for associative container inserts.
|
||||
- `singleton.h`: Simple singleton helper for cases requiring global access.
|
||||
|
||||
### 2) Graphics, geometry, and rendering
|
||||
|
||||
Rasterization, coordinate mappings, paths, grids, resampling, draw buffers, and related glue.
|
||||
|
||||
- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h`
|
||||
- Screen and tiles: `screenmap.h`, `tile2x2.h`
|
||||
- Coordinates and mappings: `xmap.h`, `xymap.h`, `screenmap.h`
|
||||
- Paths and traversal: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h`, `grid.h`, `line_simplification.h`
|
||||
- Geometry primitives: `geometry.h`, `point.h`
|
||||
- Resampling/scaling: `downscale.h`, `upscale.h`, `supersample.h`
|
||||
- Glue and UI: `leds.h`, `ui.h`, `ui_impl.h`, `rectangular_draw_buffer.h`
|
||||
- Specialized: `corkscrew.h`, `wave_simulation.h`, `wave_simulation_real.h`, `tile2x2.h`, `screenmap.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `raster.h`: Core raster interface and operations for pixel buffers.
|
||||
- `raster_sparse.h`: Sparse/partial raster representation for memory‑efficient updates.
|
||||
- `rectangular_draw_buffer.h`: Double‑buffered rectangular draw surface helpers.
|
||||
- `screenmap.h`: Maps logical coordinates to physical LED indices/layouts.
|
||||
- `tile2x2.h`: Simple 2×2 tiling utilities for composing larger surfaces.
|
||||
- `xmap.h`: General coordinate mapping utilities.
|
||||
- `xymap.h`: XY coordinate to index mapping helpers for matrices and panels.
|
||||
- `xypath.h`: Path representation in XY space for drawing and effects.
|
||||
- `xypath_impls.h`: Implementations and algorithms supporting `xypath.h`.
|
||||
- `xypath_renderer.h`: Renders paths into rasters with configurable styles.
|
||||
- `traverse_grid.h`: Grid traversal algorithms for curves/lines/fills.
|
||||
- `grid.h`: Grid data structure and iteration helpers.
|
||||
- `line_simplification.h`: Path simplification (e.g., Douglas‑Peucker‑style) for fewer segments.
|
||||
- `geometry.h`: Basic geometric computations (distances, intersections, etc.).
|
||||
- `point.h`: Small coordinate/vector primitive type.
|
||||
- `downscale.h`: Resampling utilities to reduce resolution while preserving features.
|
||||
- `upscale.h`: Upsampling utilities for enlarging frames.
|
||||
- `supersample.h`: Anti‑aliasing via multi‑sample accumulation.
|
||||
- `leds.h`: Integration helpers bridging LED buffers to rendering utilities.
|
||||
- `ui.h` / `ui_impl.h`: Minimal UI adapter hooks for demos/tests.
|
||||
- `corkscrew.h`: Experimental path/trajectory utilities for visual effects.
|
||||
- `wave_simulation.h` / `wave_simulation_real.h`: Simulated wave dynamics for organic effects.
|
||||
|
||||
### 3) Color, math, and signal processing
|
||||
|
||||
Color models, gradients, gamma, math helpers, random, noise, mapping, and basic DSP.
|
||||
|
||||
- Color and palettes: `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `gradient.h`, `fill.h`, `five_bit_hd_gamma.h`, `gamma.h`
|
||||
- Math and mapping: `math.h`, `math_macros.h`, `sin32.h`, `map_range.h`, `random.h`, `lut.h`, `clamp.h`, `clear.h`, `splat.h`, `transform.h`
|
||||
- Noise and waves: `noise_woryley.h`, `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- DSP and audio: `fft.h`, `fft_impl.h`, `audio.h`, `audio_reactive.h`
|
||||
- Time utilities: `time.h`, `time_alpha.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `colorutils.h`: High‑level color operations (blend, scale, lerp) for LED pixels.
|
||||
- `colorutils_misc.h`: Additional helpers and niche color operations.
|
||||
- `hsv.h` / `hsv16.h`: HSV color types and conversions (8‑bit and 16‑bit variants).
|
||||
- `gradient.h`: Gradient construction, sampling, and palette utilities.
|
||||
- `fill.h`: Efficient buffer/palette filling operations for pixel arrays.
|
||||
- `five_bit_hd_gamma.h`: Gamma correction tables tuned for high‑definition 5‑bit channels.
|
||||
- `gamma.h`: Gamma correction functions and LUT helpers.
|
||||
- `math.h` / `math_macros.h`: Core math primitives/macros for consistent numerics.
|
||||
- `sin32.h`: Fast fixed‑point sine approximations for animations.
|
||||
- `map_range.h`: Linear mapping and clamping between numeric ranges.
|
||||
- `random.h`: Pseudorandom utilities for effects and dithering.
|
||||
- `lut.h`: Lookup table helpers for precomputed transforms.
|
||||
- `clamp.h`: Bounds enforcement for numeric types.
|
||||
- `clear.h`: Clear/zero helpers for buffers with type awareness.
|
||||
- `splat.h`: Vectorized repeat/write helpers for bulk operations.
|
||||
- `transform.h`: Element transforms (listed here as it is often used for pixel ops too).
|
||||
- `noise_woryley.h`: Worley/cellular noise generation utilities.
|
||||
- `wave_simulation*.h`: Wavefield simulation (also referenced in graphics).
|
||||
- `fft.h` / `fft_impl.h`: Fast Fourier Transform interfaces and backends.
|
||||
- `audio.h`: Audio input/stream abstractions for host/platforms that support it.
|
||||
- `audio_reactive.h`: Utilities to drive visuals from audio features.
|
||||
- `time.h`: Timekeeping helpers (millis/micros abstractions when available).
|
||||
- `time_alpha.h`: Smoothed/exponential time‑based interpolation helpers.
|
||||
|
||||
### 4) Concurrency, async, and functional
|
||||
|
||||
Threads, synchronization, async primitives, eventing, and callable utilities.
|
||||
|
||||
- Threads and sync: `thread.h`, `mutex.h`, `thread_local.h`
|
||||
- Async primitives: `promise.h`, `promise_result.h`, `task.h`, `async.h`
|
||||
- Functional: `function.h`, `function_list.h`, `functional.h`
|
||||
- Events and engine hooks: `engine_events.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `thread.h`: Portable threading abstraction for supported hosts.
|
||||
- `mutex.h`: Mutual exclusion primitive compatible with `fl::thread`. Almost all platforms these are fake implementations.
|
||||
- `thread_local.h`: Thread‑local storage shim for supported compilers.
|
||||
- `promise.h`: Moveable wrapper around asynchronous result delivery.
|
||||
- `promise_result.h`: Result type accompanying promises/futures.
|
||||
- `task.h`: Lightweight async task primitive for orchestration.
|
||||
- `async.h`: Helpers for async composition and coordination.
|
||||
- `function.h`: Type‑erased callable wrapper analogous to `std::function`.
|
||||
- `function_list.h`: Multicast list of callables with simple invoke semantics.
|
||||
- `functional.h`: Adapters, binders, and predicates for composing callables.
|
||||
- `engine_events.h`: Event channel definitions for engine‑style systems.
|
||||
|
||||
### 5) I/O, JSON, and text/formatting
|
||||
|
||||
Streams, strings, formatted output, bytestreams, filesystem, JSON.
|
||||
|
||||
- Text and streams: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h`
|
||||
- JSON: `json.h`
|
||||
- Bytestreams and I/O: `bytestream.h`, `bytestreammemory.h`, `io.h`, `file_system.h`, `fetch.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `string.h` / `str.h`: String types and helpers without pulling in `std::string`.
|
||||
- `ostream.h` / `istream.h`: Output/input stream interfaces for host builds.
|
||||
- `sstream.h` / `strstream.h`: String‑backed stream buffers and helpers.
|
||||
- `printf.h`: Small, portable formatted print utilities.
|
||||
- `json.h`: Safe, ergonomic `fl::Json` API with defaulting operator (`|`).
|
||||
- `bytestream.h`: Sequential byte I/O abstraction for buffers/streams.
|
||||
- `bytestreammemory.h`: In‑memory byte stream implementation.
|
||||
- `io.h`: General I/O helpers for files/streams where available.
|
||||
- `file_system.h`: Minimal filesystem adapter for host platforms.
|
||||
- `fetch.h`: Basic fetch/request helpers for network‑capable hosts.
|
||||
## Comprehensive Module Breakdown
|
||||
|
||||
This section groups headers by domain, explains their role, and shows minimal usage snippets. Names shown are representative; see the header list above for the full inventory.
|
||||
|
||||
### Containers and Views
|
||||
|
||||
- Sequence and associative containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h`
|
||||
- Non‑owning and slicing: `span.h`, `slice.h`, `range_access.h`
|
||||
|
||||
Why: Embedded‑aware containers with predictable behavior across platforms. Prefer passing `fl::span<T>` to functions.
|
||||
|
||||
```cpp
|
||||
#include "fl/vector.h"
|
||||
#include "fl/span.h"
|
||||
|
||||
size_t count_nonzero(fl::span<const uint8_t> bytes) {
|
||||
size_t count = 0;
|
||||
for (uint8_t b : bytes) { if (b != 0) { ++count; } }
|
||||
return count;
|
||||
}
|
||||
```
|
||||
|
||||
### Strings and Streams
|
||||
|
||||
- Text types and streaming: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h`
|
||||
|
||||
Why: Consistent string/stream facilities without pulling in the standard streams.
|
||||
|
||||
```cpp
|
||||
#include "fl/string.h"
|
||||
#include "fl/sstream.h"
|
||||
|
||||
fl::string greet(const fl::string& name) {
|
||||
fl::sstream ss;
|
||||
ss << "Hello, " << name << "!";
|
||||
return ss.str();
|
||||
}
|
||||
```
|
||||
|
||||
### Memory and Ownership
|
||||
|
||||
- Smart pointers and utilities: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `allocator.h`, `memory.h`, `memfill.h`
|
||||
|
||||
Why: RAII ownership with explicit semantics. Prefer `fl::make_shared<T>()`/`fl::make_unique<T>()` patterns where available, or direct constructors provided by these headers.
|
||||
|
||||
```cpp
|
||||
#include "fl/shared_ptr.h"
|
||||
|
||||
struct Widget { int value; };
|
||||
|
||||
void ownership_example() {
|
||||
fl::shared_ptr<Widget> w(new Widget{123});
|
||||
auto w2 = w; // shared ownership
|
||||
}
|
||||
```
|
||||
|
||||
### Functional Utilities
|
||||
|
||||
- Callables and lists: `function.h`, `function_list.h`, `functional.h`
|
||||
|
||||
Why: Store callbacks and multicast them safely.
|
||||
|
||||
```cpp
|
||||
#include "fl/function_list.h"
|
||||
|
||||
void on_event(int code) { /* ... */ }
|
||||
|
||||
void register_handlers() {
|
||||
fl::function_list<void(int)> handlers;
|
||||
handlers.add(on_event);
|
||||
handlers(200); // invoke all
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrency and Async
|
||||
|
||||
- Threads and synchronization: `thread.h`, `mutex.h`, `thread_local.h`
|
||||
- Async primitives: `promise.h`, `promise_result.h`, `task.h`
|
||||
|
||||
Why: Lightweight foundations for parallel work or async orchestration where supported.
|
||||
|
||||
```cpp
|
||||
#include "fl/promise.h"
|
||||
|
||||
fl::promise<int> compute_async(); // returns a moveable wrapper around a future-like result
|
||||
```
|
||||
|
||||
### Math, Random, and DSP
|
||||
|
||||
- Core math and helpers: `math.h`, `math_macros.h`, `sin32.h`, `random.h`, `map_range.h`
|
||||
- Color math: `gamma.h`, `gradient.h`, `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `fill.h`
|
||||
- FFT and analysis: `fft.h`, `fft_impl.h`
|
||||
|
||||
Why: Efficient numeric operations for LED effects, audio reactivity, and transforms.
|
||||
|
||||
```cpp
|
||||
#include "fl/gamma.h"
|
||||
|
||||
uint8_t apply_gamma(uint8_t v) {
|
||||
return fl::gamma::correct8(v);
|
||||
}
|
||||
```
|
||||
|
||||
### Geometry and Grids
|
||||
|
||||
- Basic geometry: `point.h`, `geometry.h`
|
||||
- Grid traversal and simplification: `grid.h`, `traverse_grid.h`, `line_simplification.h`
|
||||
|
||||
Why: Building blocks for 2D/3D layouts and path operations.
|
||||
|
||||
### Graphics, Rasterization, and Resampling
|
||||
|
||||
- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h`
|
||||
- Screen mapping and tiling: `screenmap.h`, `tile2x2.h`, `xmap.h`, `xymap.h`
|
||||
- Paths and renderers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h`
|
||||
- Resampling and effects: `downscale.h`, `upscale.h`, `supersample.h`
|
||||
|
||||
Why: Efficient frame manipulation for LED matrices and coordinate spaces.
|
||||
|
||||
```cpp
|
||||
#include "fl/downscale.h"
|
||||
|
||||
// Downscale a high‑res buffer to a target raster (API varies by adapter)
|
||||
```
|
||||
|
||||
#### Graphics Deep Dive
|
||||
|
||||
This section explains how the major graphics utilities fit together and how to use them effectively for high‑quality, high‑performance rendering on LED strips, matrices, and complex shapes.
|
||||
|
||||
- **Wave simulation (1D/2D)**
|
||||
- Headers: `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- Concepts:
|
||||
- Super‑sampling for quality: choose `SuperSample` factors to run an internal high‑resolution simulation and downsample for display.
|
||||
- Consistent speed at higher quality: call `setExtraFrames(u8)` to update the simulation multiple times per frame to maintain perceived speed when super‑sampling.
|
||||
- Output accessors: `getf`, `geti16`, `getu8` return float/fixed/byte values; 2D version offers cylindrical wrapping via `setXCylindrical(true)`.
|
||||
- Tips for “faster” updates:
|
||||
- Use `setExtraFrames()` to advance multiple internal steps per visual frame without changing your outer timing.
|
||||
- Prefer `getu8(...)` when feeding color functions or gradients on constrained devices.
|
||||
|
||||
- **fl::Leds – array and mapped matrix**
|
||||
- Header: `leds.h`; mapping: `xymap.h`
|
||||
- `fl::Leds` wraps a `CRGB*` so you can treat it as:
|
||||
- A plain `CRGB*` via implicit conversion for classic FastLED APIs.
|
||||
- A 2D surface via `operator()(x, y)` that respects an `XYMap` (serpentine, line‑by‑line, LUT, or custom function).
|
||||
- Construction:
|
||||
- `Leds(CRGB* leds, u16 width, u16 height)` for quick serpentine/rectangular usage.
|
||||
- `Leds(CRGB* leds, const XYMap& xymap)` for full control (serpentine, rectangular, user function, or LUT).
|
||||
- Access and safety:
|
||||
- `at(x, y)`/`operator()(x, y)` map to the correct LED index; out‑of‑bounds is safe and returns a sentinel.
|
||||
- `operator[]` exposes row‑major access when the map is serpentine or line‑by‑line.
|
||||
|
||||
- **Matrix mapping (XYMap)**
|
||||
- Header: `xymap.h`
|
||||
- Create maps for common layouts:
|
||||
- `XYMap::constructSerpentine(w, h)` for typical pre‑wired panels.
|
||||
- `XYMap::constructRectangularGrid(w, h)` for row‑major matrices.
|
||||
- `XYMap::constructWithUserFunction(w, h, XYFunction)` for custom wiring.
|
||||
- `XYMap::constructWithLookUpTable(...)` for arbitrary wiring via LUT.
|
||||
- Utilities:
|
||||
- `mapToIndex(x, y)` maps coordinates to strip index.
|
||||
- `has(x, y)` tests bounds; `toScreenMap()` converts to a float UI mapping.
|
||||
|
||||
- **XY paths and path rendering (xypath)**
|
||||
- Headers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, helpers in `tile2x2.h`, `transform.h`.
|
||||
- Purpose: parameterized paths in
|
||||
\([0,1] \to (x,y)\) for drawing lines/curves/shapes with subpixel precision.
|
||||
- Ready‑made paths: point, line, circle, heart, Archimedean spiral, rose curves, phyllotaxis, Gielis superformula, and Catmull‑Rom splines with editable control points.
|
||||
- Rendering:
|
||||
- Subpixel sampling via `Tile2x2_u8` enables high quality on low‑res matrices.
|
||||
- Use `XYPath::drawColor` or `drawGradient` to rasterize into an `fl::Leds` surface.
|
||||
- `XYPath::rasterize` writes into a sparse raster for advanced composition.
|
||||
- Transforms and bounds:
|
||||
- `setDrawBounds(w, h)` and `setTransform(TransformFloat)` control framing and animation transforms.
|
||||
|
||||
- **Subpixel “splat” rendering (Tile2x2)**
|
||||
- Header: `tile2x2.h`
|
||||
- Concept: represent a subpixel footprint as a 2×2 tile of coverage values (u8 alphas). When a subpixel position moves between LEDs, neighboring LEDs get proportional contributions.
|
||||
- Use cases:
|
||||
- `Tile2x2_u8::draw(color, xymap, out)` to composite with color per‑pixel.
|
||||
- Custom blending: `tile.draw(xymap, visitor)` to apply your own alpha/compositing.
|
||||
- Wrapped tiles: `Tile2x2_u8_wrap` supports cylindrical wrap with interpolation for continuous effects.
|
||||
|
||||
- **Downscale**
|
||||
- Header: `downscale.h`
|
||||
- Purpose: resample from a higher‑resolution buffer to a smaller target, preserving features.
|
||||
- APIs:
|
||||
- `downscale(src, srcXY, dst, dstXY)` general case.
|
||||
- `downscaleHalf(...)` optimized 2× reduction (square or mapped) used automatically when sizes match.
|
||||
|
||||
- **Upscale**
|
||||
- Header: `upscale.h`
|
||||
- Purpose: bilinear upsampling from a low‑res buffer to a larger target.
|
||||
- APIs:
|
||||
- `upscale(input, output, inW, inH, xyMap)` auto‑selects optimized paths.
|
||||
- `upscaleRectangular` and `upscaleRectangularPowerOf2` bypass XY mapping for straight row‑major layouts.
|
||||
- Float reference versions exist for validation.
|
||||
|
||||
- **Corkscrew (cylindrical projection)**
|
||||
- Header: `corkscrew.h`
|
||||
- Goal: draw into a rectangular buffer and project onto a tightly wrapped helical/cylindrical LED layout.
|
||||
- Inputs and sizing:
|
||||
- `CorkscrewInput{ totalTurns, numLeds, Gap, invert }` defines geometry; helper `calculateWidth()/calculateHeight()` provide rectangle dimensions for buffer allocation.
|
||||
- Mapping and iteration:
|
||||
- `Corkscrew::at_exact(i)` returns the exact position for LED i; `at_wrap(float)` returns a wrapped `Tile2x2_u8_wrap` footprint for subpixel‑accurate sampling.
|
||||
- Use `toScreenMap(diameter)` to produce a `ScreenMap` for UI overlays or browser visualization.
|
||||
- Rectangular buffer integration:
|
||||
- `getBuffer()/data()` provide a lazily‑initialized rectangle; `fillBuffer/clearBuffer` manage it.
|
||||
- `readFrom(source_grid, use_multi_sampling)` projects from a high‑def source grid to the corkscrew using multi‑sampling for quality.
|
||||
|
||||
- Examples
|
||||
|
||||
1) Manual per‑frame draw (push every frame)
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/time.h"
|
||||
|
||||
// Your physical LED buffer
|
||||
constexpr uint16_t NUM_LEDS = 144;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
// Define a corkscrew geometry (e.g., 19 turns tightly wrapped)
|
||||
fl::Corkscrew::Input input(/*totalTurns=*/19.0f, /*numLeds=*/NUM_LEDS);
|
||||
fl::Corkscrew cork(input);
|
||||
|
||||
// A simple rectangular source grid to draw into (unwrapped cylinder)
|
||||
// Match the corkscrew's recommended rectangle
|
||||
const uint16_t W = cork.cylinder_width();
|
||||
const uint16_t H = cork.cylinder_height();
|
||||
fl::Grid<CRGB> rect(W, H);
|
||||
|
||||
void draw_pattern(uint32_t t_ms) {
|
||||
// Example: time‑animated gradient on the unwrapped cylinder
|
||||
for (uint16_t y = 0; y < H; ++y) {
|
||||
for (uint16_t x = 0; x < W; ++x) {
|
||||
uint8_t hue = uint8_t((x * 255u) / (W ? W : 1)) + uint8_t((t_ms / 10) & 0xFF);
|
||||
rect(x, y) = CHSV(hue, 255, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void project_to_strip() {
|
||||
// For each LED index i on the physical strip, sample the unwrapped
|
||||
// rectangle using the subpixel footprint from at_wrap(i)
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
auto tile = cork.at_wrap(float(i));
|
||||
// tile.at(u,v) gives {absolute_pos, alpha}
|
||||
// Blend the 2x2 neighborhood from the rectangular buffer
|
||||
uint16_t r = 0, g = 0, b = 0, a_sum = 0;
|
||||
for (uint16_t vy = 0; vy < 2; ++vy) {
|
||||
for (uint16_t vx = 0; vx < 2; ++vx) {
|
||||
auto entry = tile.at(vx, vy); // pair<vec2i16, u8>
|
||||
const auto pos = entry.first; // absolute cylinder coords
|
||||
const uint8_t a = entry.second; // alpha 0..255
|
||||
if (pos.x >= 0 && pos.x < int(W) && pos.y >= 0 && pos.y < int(H) && a > 0) {
|
||||
const CRGB& c = rect(uint16_t(pos.x), uint16_t(pos.y));
|
||||
r += uint16_t(c.r) * a;
|
||||
g += uint16_t(c.g) * a;
|
||||
b += uint16_t(c.b) * a;
|
||||
a_sum += a;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (a_sum == 0) {
|
||||
leds[i] = CRGB::Black;
|
||||
} else {
|
||||
leds[i].r = uint8_t(r / a_sum);
|
||||
leds[i].g = uint8_t(g / a_sum);
|
||||
leds[i].b = uint8_t(b / a_sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t t = fl::time();
|
||||
draw_pattern(t);
|
||||
project_to_strip();
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
2) Automate with task::before_frame (draw right before render)
|
||||
|
||||
Use the per‑frame task API; no direct EngineEvents binding is needed. Per‑frame tasks are scheduled via `fl::task` and integrated with the frame lifecycle by the async system.
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/task.h"
|
||||
#include "fl/async.h"
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/grid.h"
|
||||
|
||||
constexpr uint16_t NUM_LEDS = 144;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
fl::Corkscrew cork(fl::Corkscrew::Input(19.0f, NUM_LEDS));
|
||||
const uint16_t W = cork.cylinder_width();
|
||||
const uint16_t H = cork.cylinder_height();
|
||||
fl::Grid<CRGB> rect(W, H);
|
||||
|
||||
static void draw_pattern(uint32_t t_ms, fl::Grid<CRGB>& dst);
|
||||
static void project_to_strip(const fl::Corkscrew& c, const fl::Grid<CRGB>& src, CRGB* out, uint16_t n);
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
|
||||
// Register a before_frame task that runs immediately before each render
|
||||
fl::task::before_frame().then([&](){
|
||||
uint32_t t = fl::time();
|
||||
draw_pattern(t, rect);
|
||||
project_to_strip(cork, rect, leds, NUM_LEDS);
|
||||
});
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The before_frame task is invoked automatically at the right time
|
||||
FastLED.show();
|
||||
// Optionally pump other async work
|
||||
fl::async_yield();
|
||||
}
|
||||
```
|
||||
|
||||
- The manual approach gives explicit control each frame.
|
||||
- The `task::before_frame()` approach schedules work just‑in‑time before rendering without manual event wiring. Use `task::after_frame()` for post‑render work.
|
||||
|
||||
- **High‑definition HSV16**
|
||||
- Headers: `hsv16.h`, implementation in `hsv16.cpp`
|
||||
- `fl::HSV16` stores 16‑bit H/S/V for high‑precision conversion to `CRGB` without banding; construct from `CRGB` or manually, and convert via `ToRGB()` or implicit cast.
|
||||
- Color boost:
|
||||
- `HSV16::colorBoost(EaseType saturation_function, EaseType luminance_function)` applies a saturation‑space boost similar to gamma, tuned separately for saturation and luminance to counter LED gamut/compression (e.g., WS2812).
|
||||
|
||||
- **Easing functions (accurate 8/16‑bit)**
|
||||
- Header: `ease.h`
|
||||
- Accurate ease‑in/out functions with 8‑ and 16‑bit variants for quad/cubic/sine families, plus dispatchers: `ease8(type, ...)`, `ease16(type, ...)`.
|
||||
- Use to shape animation curves, palette traversal, or time alpha outputs.
|
||||
|
||||
- **Time alpha**
|
||||
- Header: `time_alpha.h`
|
||||
- Helpers for time‑based interpolation: `time_alpha8/16/f` compute progress in a window `[start, end]`.
|
||||
- Stateful helpers:
|
||||
- `TimeRamp(rise, latch, fall)` for a full rise‑hold‑fall cycle.
|
||||
- `TimeClampedTransition(duration)` for a clamped one‑shot.
|
||||
- Use cases: envelope control for brightness, effect blending, or gating simulation energy over time.
|
||||
|
||||
### JSON Utilities
|
||||
|
||||
- Safe JSON access: `json.h`
|
||||
|
||||
Why: Ergonomic, crash‑resistant access with defaulting operator (`|`). Prefer the `fl::Json` API for new code.
|
||||
|
||||
```cpp
|
||||
fl::Json cfg = fl::Json::parse("{\"enabled\":true}");
|
||||
bool enabled = cfg["enabled"] | false;
|
||||
```
|
||||
|
||||
### I/O and Filesystem
|
||||
|
||||
- Basic I/O and streams: `io.h`, `ostream.h`, `istream.h`, `sstream.h`, `printf.h`
|
||||
- Filesystem adapters: `file_system.h`
|
||||
|
||||
Why: Cross‑platform text formatting, buffered I/O, and optional file access on host builds.
|
||||
|
||||
### Algorithms and Utilities
|
||||
|
||||
- Algorithms and transforms: `algorithm.h`, `transform.h`, `range_access.h`
|
||||
- Compile‑time utilities: `type_traits.h`, `tuple.h`, `variant.h`, `optional.h`, `utility.h`, `initializer_list.h`, `template_magic.h`, `types.h`, `stdint.h`, `namespace.h`
|
||||
- Platform shims and control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h`
|
||||
|
||||
Why: Familiar patterns with embedded‑appropriate implementations and compiler‑portable controls.
|
||||
|
||||
### Audio and Reactive Systems
|
||||
|
||||
### UI System (JSON UI)
|
||||
|
||||
FastLED includes a JSON‑driven UI layer that can expose controls (sliders, buttons, checkboxes, number fields, dropdowns, titles, descriptions, help, audio visualizers) for interactive demos and remote control.
|
||||
|
||||
- **Availability by platform**
|
||||
- **AVR and other low‑memory chipsets**: disabled by default. The UI is not compiled in on constrained targets.
|
||||
- **WASM build**: enabled. The web UI is available and connects to the running sketch.
|
||||
- **Other platforms**: can be enabled if the platform supplies a bridge. See `platforms/ui_defs.h` for the compile‑time gate and platform includes.
|
||||
|
||||
- **Key headers and switches**
|
||||
- `platforms/ui_defs.h`: controls `FASTLED_USE_JSON_UI` (defaults to 1 on WASM, 0 elsewhere).
|
||||
- `fl/ui.h`: C++ UI element classes (UISlider, UIButton, UICheckbox, UINumberField, UIDropdown, UITitle, UIDescription, UIHelp, UIAudio, UIGroup).
|
||||
- `platforms/shared/ui/json/ui_manager.h`: platform‑agnostic JSON UI manager that integrates with `EngineEvents`.
|
||||
- `platforms/shared/ui/json/readme.md`: implementation guide and JSON protocol.
|
||||
|
||||
- **Lifecycle and data flow**
|
||||
- The UI system uses an update manager (`JsonUiManager`) and is integrated with `EngineEvents`:
|
||||
- On frame lifecycle events, new UI elements are exported as JSON; inbound updates are processed after frames.
|
||||
- This enables remote control: UI state can be driven locally (browser) or by remote senders issuing JSON updates.
|
||||
- Send (sketch → UI): when components are added or changed, `JsonUiManager` emits JSON to the platform bridge.
|
||||
- Receive (UI → sketch): the platform calls `JsonUiManager::updateUiComponents(const char*)` with a JSON object; changes are applied on `onEndFrame()`.
|
||||
|
||||
- **Registering handlers to send/receive JSON**
|
||||
- Platform bridge constructs the manager with a function that forwards JSON to the UI:
|
||||
- `JsonUiManager(Callback updateJs)` where `updateJs(const char*)` transports JSON to the front‑end (WASM example uses JS bindings in `src/platforms/wasm/ui.cpp`).
|
||||
- To receive UI updates from the front‑end, call:
|
||||
- `MyPlatformUiManager::instance().updateUiComponents(json_str)` to queue changes; `onEndFrame()` applies them.
|
||||
|
||||
- **Sketch‑side usage examples**
|
||||
|
||||
Basic setup with controls and groups:
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/ui.h"
|
||||
|
||||
UISlider brightness("Brightness", 128, 0, 255);
|
||||
UICheckbox enabled("Enabled", true);
|
||||
UIButton reset("Reset");
|
||||
UIDropdown mode("Mode", {fl::string("Rainbow"), fl::string("Waves"), fl::string("Solid")});
|
||||
UITitle title("Demo Controls");
|
||||
UIDescription desc("Adjust parameters in real time");
|
||||
UIHelp help("# Help\nUse the controls to tweak the effect.");
|
||||
|
||||
void setup() {
|
||||
// Group controls visually in the UI
|
||||
UIGroup group("Main", title, desc, brightness, enabled, mode, reset, help);
|
||||
|
||||
// Callbacks are pumped via EngineEvents; they trigger when values change
|
||||
brightness.onChanged([](UISlider& s){ FastLED.setBrightness((uint8_t)s); });
|
||||
enabled.onChanged([](UICheckbox& c){ /* toggle effect */ });
|
||||
mode.onChanged([](UIDropdown& d){ /* change program by d.as_int() */ });
|
||||
reset.onClicked([](){ /* reset animation state */ });
|
||||
}
|
||||
```
|
||||
|
||||
Help component (see `examples/UITest/UITest.ino`):
|
||||
```cpp
|
||||
UIHelp helpMarkdown(
|
||||
R"(# FastLED UI\nThis area supports markdown, code blocks, and links.)");
|
||||
helpMarkdown.setGroup("Documentation");
|
||||
```
|
||||
|
||||
Audio input UI (WASM‑enabled platforms):
|
||||
```cpp
|
||||
UIAudio audio("Mic");
|
||||
void loop() {
|
||||
while (audio.hasNext()) {
|
||||
auto sample = audio.next();
|
||||
// Use sample data for visualizations
|
||||
}
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
- **Platform bridge: sending and receiving JSON**
|
||||
- Sending from sketch to UI: `JsonUiManager` invokes the `updateJs(const char*)` callback with JSON representing either:
|
||||
- A JSON array of element definitions (on first export)
|
||||
- A JSON object of state updates (on changes)
|
||||
- Receiving from UI to sketch: the platform calls
|
||||
- `JsonUiManager::updateUiComponents(const char* jsonStr)` where `jsonStr` is a JSON object like:
|
||||
`{ "id_123": {"value": 200}, "id_456": {"pressed": true } }`
|
||||
- The manager defers application until the end of the current frame via `onEndFrame()` to ensure consistency.
|
||||
|
||||
- **WASM specifics**
|
||||
- WASM builds provide the web UI and JS glue (see `src/platforms/wasm/ui.cpp` and `src/platforms/wasm/compiler/modules/ui_manager.js`).
|
||||
- Element definitions may be routed directly to the browser UI manager to avoid feedback loops; updates are logged to an inspector, and inbound messages drive `updateUiComponents`.
|
||||
|
||||
- **Enabling on other platforms**
|
||||
- Implement a platform UI manager using `JsonUiManager` and provide the two weak functions the UI elements call:
|
||||
- `void addJsonUiComponent(fl::weak_ptr<JsonUiInternal>)`
|
||||
- `void removeJsonUiComponent(fl::weak_ptr<JsonUiInternal>)`
|
||||
- Forward platform UI events into `updateUiComponents(jsonStr)`.
|
||||
- Ensure `EngineEvents` are active so UI updates are exported and applied on frame boundaries.
|
||||
|
||||
- Audio adapters and analysis: `audio.h`, `audio_reactive.h`
|
||||
- Engine hooks: `engine_events.h`
|
||||
|
||||
Why: Build effects that respond to input signals.
|
||||
|
||||
### Miscellaneous and Specialized
|
||||
|
||||
- Wave simulation: `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- Screen and LED glue: `leds.h`, `screenmap.h`
|
||||
- Path/visual experimentation: `corkscrew.h`
|
||||
|
||||
---
|
||||
|
||||
## Guidance for New Users
|
||||
|
||||
- If you are writing sketches: include `FastLED.h` and follow examples in `examples/`. The `fl::` headers power those features under the hood.
|
||||
- If you are extending FastLED internals or building advanced effects: prefer `fl::` containers and `fl::span` over STL equivalents to maintain portability.
|
||||
- Favor smart pointers and moveable wrappers for resource management. Avoid raw pointers and manual `delete`.
|
||||
- Use `fl::Json` for robust JSON handling with safe defaults.
|
||||
|
||||
## Guidance for C++ Developers
|
||||
|
||||
- Treat `fl::` as an embedded‑friendly STL. Many concepts mirror the standard library but are tuned for constrained targets and consistent compiler support.
|
||||
- When designing APIs, prefer non‑owning views (`fl::span<T>`) for input parameters and explicit ownership types for storage.
|
||||
- Use `compiler_control.h` macros for warning control and portability instead of raw pragmas.
|
||||
|
||||
---
|
||||
|
||||
This README will evolve alongside the codebase. If you are exploring a specific subsystem, start from the relevant headers listed above and follow includes to supporting modules.
|
||||
15
libraries/FastLED/src/fl/_readme
Normal file
15
libraries/FastLED/src/fl/_readme
Normal file
@@ -0,0 +1,15 @@
|
||||
This directory holds core functionality of FastLED.
|
||||
|
||||
Every class/struct/function in this directory must be under the
|
||||
namespace fl, except for special cases.
|
||||
|
||||
This is done because according to the the Arduino build system,
|
||||
all headers/cpp/hpp files in the root the src directory will be in global
|
||||
scope and other libraries can #include them by mistake.
|
||||
|
||||
This has happened so many times that I've just gone ahead and migrated all
|
||||
the new code to this directory, to prevent name collisions.
|
||||
|
||||
# When to put stuff in `fl` and not `fx`
|
||||
|
||||
If it's a visualizer, put it in the `fx` fodler. If it it's something to help to build a visualizer then you put it here.
|
||||
565
libraries/FastLED/src/fl/algorithm.h
Normal file
565
libraries/FastLED/src/fl/algorithm.h
Normal file
@@ -0,0 +1,565 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/random.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename Iterator>
|
||||
void reverse(Iterator first, Iterator last) {
|
||||
while ((first != last) && (first != --last)) {
|
||||
swap(*first++, *last);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator max_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*max_iter < *first) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator max_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*max_iter, *first)) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator min_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*first < *min_iter) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator min_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*first, *min_iter)) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) {
|
||||
while (first1 != last1) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) {
|
||||
while (first1 != last1) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, BinaryPredicate pred) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2>
|
||||
bool equal_container(const Container1& c1, const Container2& c2) {
|
||||
fl::size size1 = c1.size();
|
||||
fl::size size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end());
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2, typename BinaryPredicate>
|
||||
bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) {
|
||||
fl::size size1 = c1.size();
|
||||
fl::size size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred);
|
||||
}
|
||||
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
void fill(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
*first = value;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
Iterator find(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
if (*first == value) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator find_if(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
while (first != last) {
|
||||
if (pred(*first)) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator find_if_not(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
while (first != last) {
|
||||
if (!pred(*first)) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
Iterator remove(Iterator first, Iterator last, const T& value) {
|
||||
Iterator result = first;
|
||||
while (first != last) {
|
||||
if (!(*first == value)) {
|
||||
if (result != first) {
|
||||
*result = fl::move(*first);
|
||||
}
|
||||
++result;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator remove_if(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
Iterator result = first;
|
||||
while (first != last) {
|
||||
if (!pred(*first)) {
|
||||
if (result != first) {
|
||||
*result = fl::move(*first);
|
||||
}
|
||||
++result;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Insertion sort implementation for small arrays
|
||||
template <typename Iterator, typename Compare>
|
||||
void insertion_sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) return;
|
||||
|
||||
for (Iterator i = first + 1; i != last; ++i) {
|
||||
auto value = fl::move(*i);
|
||||
Iterator j = i;
|
||||
|
||||
while (j != first && comp(value, *(j - 1))) {
|
||||
*j = fl::move(*(j - 1));
|
||||
--j;
|
||||
}
|
||||
|
||||
*j = fl::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Median-of-three pivot selection
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator median_of_three(Iterator first, Iterator middle, Iterator last, Compare comp) {
|
||||
if (comp(*middle, *first)) {
|
||||
if (comp(*last, *middle)) {
|
||||
return middle;
|
||||
} else if (comp(*last, *first)) {
|
||||
return last;
|
||||
} else {
|
||||
return first;
|
||||
}
|
||||
} else {
|
||||
if (comp(*last, *first)) {
|
||||
return first;
|
||||
} else if (comp(*last, *middle)) {
|
||||
return last;
|
||||
} else {
|
||||
return middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Partition function for quicksort
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator partition(Iterator first, Iterator last, Compare comp) {
|
||||
Iterator middle = first + (last - first) / 2;
|
||||
Iterator pivot_iter = median_of_three(first, middle, last - 1, comp);
|
||||
|
||||
// Move pivot to end
|
||||
swap(*pivot_iter, *(last - 1));
|
||||
Iterator pivot = last - 1;
|
||||
|
||||
Iterator i = first;
|
||||
for (Iterator j = first; j != pivot; ++j) {
|
||||
if (comp(*j, *pivot)) {
|
||||
swap(*i, *j);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
swap(*i, *pivot);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Heapsort implementation (fallback for deep recursion)
|
||||
template <typename Iterator, typename Compare>
|
||||
void sift_down(Iterator first, Iterator start, Iterator end, Compare comp) {
|
||||
Iterator root = start;
|
||||
|
||||
while (root - first <= (end - first - 2) / 2) {
|
||||
Iterator child = first + 2 * (root - first) + 1;
|
||||
Iterator swap_iter = root;
|
||||
|
||||
if (comp(*swap_iter, *child)) {
|
||||
swap_iter = child;
|
||||
}
|
||||
|
||||
if (child + 1 <= end && comp(*swap_iter, *(child + 1))) {
|
||||
swap_iter = child + 1;
|
||||
}
|
||||
|
||||
if (swap_iter == root) {
|
||||
return;
|
||||
} else {
|
||||
swap(*root, *swap_iter);
|
||||
root = swap_iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
void heapify(Iterator first, Iterator last, Compare comp) {
|
||||
Iterator start = first + (last - first - 2) / 2;
|
||||
|
||||
while (true) {
|
||||
sift_down(first, start, last - 1, comp);
|
||||
if (start == first) {
|
||||
break;
|
||||
}
|
||||
--start;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
void heap_sort(Iterator first, Iterator last, Compare comp) {
|
||||
heapify(first, last, comp);
|
||||
|
||||
Iterator end = last - 1;
|
||||
while (end > first) {
|
||||
swap(*end, *first);
|
||||
sift_down(first, first, end - 1, comp);
|
||||
--end;
|
||||
}
|
||||
}
|
||||
|
||||
// Quicksort implementation
|
||||
template <typename Iterator, typename Compare>
|
||||
void quicksort_impl(Iterator first, Iterator last, Compare comp) {
|
||||
if (last - first <= 16) { // Use insertion sort for small arrays
|
||||
insertion_sort(first, last, comp);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator pivot = partition(first, last, comp);
|
||||
quicksort_impl(first, pivot, comp);
|
||||
quicksort_impl(pivot + 1, last, comp);
|
||||
}
|
||||
|
||||
// Rotate elements in range [first, last) so that middle becomes the new first
|
||||
template <typename Iterator>
|
||||
void rotate_impl(Iterator first, Iterator middle, Iterator last) {
|
||||
if (first == middle || middle == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator next = middle;
|
||||
while (first != next) {
|
||||
swap(*first++, *next++);
|
||||
if (next == last) {
|
||||
next = middle;
|
||||
} else if (first == middle) {
|
||||
middle = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the position where value should be inserted in sorted range [first, last)
|
||||
template <typename Iterator, typename T, typename Compare>
|
||||
Iterator lower_bound_impl(Iterator first, Iterator last, const T& value, Compare comp) {
|
||||
auto count = last - first;
|
||||
while (count > 0) {
|
||||
auto step = count / 2;
|
||||
Iterator it = first + step;
|
||||
if (comp(*it, value)) {
|
||||
first = ++it;
|
||||
count -= step + 1;
|
||||
} else {
|
||||
count = step;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
// In-place merge operation for merge sort (stable sort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void merge_inplace(Iterator first, Iterator middle, Iterator last, Compare comp) {
|
||||
// If one of the ranges is empty, nothing to merge
|
||||
if (first == middle || middle == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If arrays are small enough, use insertion-based merge
|
||||
auto left_size = middle - first;
|
||||
auto right_size = last - middle;
|
||||
if (left_size + right_size <= 32) {
|
||||
// Simple insertion-based merge for small arrays
|
||||
Iterator left = first;
|
||||
Iterator right = middle;
|
||||
|
||||
while (left < middle && right < last) {
|
||||
if (!comp(*right, *left)) {
|
||||
// left element is in correct position
|
||||
++left;
|
||||
} else {
|
||||
// right element needs to be inserted into left part
|
||||
auto value = fl::move(*right);
|
||||
Iterator shift_end = right;
|
||||
Iterator shift_start = left;
|
||||
|
||||
// Shift elements to make room
|
||||
while (shift_end > shift_start) {
|
||||
*shift_end = fl::move(*(shift_end - 1));
|
||||
--shift_end;
|
||||
}
|
||||
|
||||
*left = fl::move(value);
|
||||
++left;
|
||||
++middle; // middle has shifted right
|
||||
++right;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For larger arrays, use rotation-based merge
|
||||
if (left_size == 0 || right_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (left_size == 1) {
|
||||
// Find insertion point for the single left element in right array
|
||||
Iterator pos = lower_bound_impl(middle, last, *first, comp);
|
||||
rotate_impl(first, middle, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (right_size == 1) {
|
||||
// Find insertion point for the single right element in left array
|
||||
Iterator pos = lower_bound_impl(first, middle, *(last - 1), comp);
|
||||
rotate_impl(pos, middle, last);
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide both arrays and recursively merge
|
||||
Iterator left_mid = first + left_size / 2;
|
||||
Iterator right_mid = lower_bound_impl(middle, last, *left_mid, comp);
|
||||
|
||||
// Rotate to bring the two middle parts together
|
||||
rotate_impl(left_mid, middle, right_mid);
|
||||
|
||||
// Update middle position
|
||||
Iterator new_middle = left_mid + (right_mid - middle);
|
||||
|
||||
// Recursively merge the two parts
|
||||
merge_inplace(first, left_mid, new_middle, comp);
|
||||
merge_inplace(new_middle, right_mid, last, comp);
|
||||
}
|
||||
|
||||
// Merge sort implementation (stable, in-place)
|
||||
template <typename Iterator, typename Compare>
|
||||
void mergesort_impl(Iterator first, Iterator last, Compare comp) {
|
||||
auto size = last - first;
|
||||
if (size <= 16) { // Use insertion sort for small arrays (it's stable)
|
||||
insertion_sort(first, last, comp);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator middle = first + size / 2;
|
||||
mergesort_impl(first, middle, comp);
|
||||
mergesort_impl(middle, last, comp);
|
||||
merge_inplace(first, middle, last, comp);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Sort function with custom comparator (using quicksort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last || first + 1 == last) {
|
||||
return; // Already sorted or empty
|
||||
}
|
||||
|
||||
detail::quicksort_impl(first, last, comp);
|
||||
}
|
||||
|
||||
// Sort function with default comparator
|
||||
template <typename Iterator>
|
||||
void sort(Iterator first, Iterator last) {
|
||||
// Use explicit template parameter to avoid C++14 auto in lambda
|
||||
typedef typename fl::remove_reference<decltype(*first)>::type value_type;
|
||||
sort(first, last, [](const value_type& a, const value_type& b) { return a < b; });
|
||||
}
|
||||
|
||||
// Stable sort function with custom comparator (using merge sort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void stable_sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last || first + 1 == last) {
|
||||
return; // Already sorted or empty
|
||||
}
|
||||
|
||||
detail::mergesort_impl(first, last, comp);
|
||||
}
|
||||
|
||||
// Stable sort function with default comparator
|
||||
template <typename Iterator>
|
||||
void stable_sort(Iterator first, Iterator last) {
|
||||
// Use explicit template parameter to avoid C++14 auto in lambda
|
||||
typedef typename fl::remove_reference<decltype(*first)>::type value_type;
|
||||
stable_sort(first, last, [](const value_type& a, const value_type& b) { return a < b; });
|
||||
}
|
||||
|
||||
// Shuffle function with custom random generator (Fisher-Yates shuffle)
|
||||
template <typename Iterator, typename RandomGenerator>
|
||||
void shuffle(Iterator first, Iterator last, RandomGenerator& g) {
|
||||
if (first == last) {
|
||||
return; // Empty range, nothing to shuffle
|
||||
}
|
||||
|
||||
auto n = last - first;
|
||||
for (auto i = n - 1; i > 0; --i) {
|
||||
// Generate random index from 0 to i (inclusive)
|
||||
auto j = g() % (i + 1);
|
||||
|
||||
// Swap elements at positions i and j
|
||||
swap(*(first + i), *(first + j));
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle function with fl::fl_random instance
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator first, Iterator last, fl_random& rng) {
|
||||
if (first == last) {
|
||||
return; // Empty range, nothing to shuffle
|
||||
}
|
||||
|
||||
auto n = last - first;
|
||||
for (auto i = n - 1; i > 0; --i) {
|
||||
// Generate random index from 0 to i (inclusive)
|
||||
auto j = rng(static_cast<u32>(i + 1));
|
||||
|
||||
// Swap elements at positions i and j
|
||||
swap(*(first + i), *(first + j));
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle function with default random generator
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator first, Iterator last) {
|
||||
shuffle(first, last, default_random());
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
18
libraries/FastLED/src/fl/align.h
Normal file
18
libraries/FastLED/src/fl/align.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
#if defined(FASTLED_TESTING) || defined(__EMSCRIPTEN__)
|
||||
// max_align_t and alignof
|
||||
#include <cstddef> // ok include
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define FL_ALIGN_BYTES 8
|
||||
#define FL_ALIGN alignas(FL_ALIGN_BYTES)
|
||||
#define FL_ALIGN_AS(T) alignas(alignof(T))
|
||||
#else
|
||||
#define FL_ALIGN_BYTES 1
|
||||
#define FL_ALIGN
|
||||
#define FL_ALIGN_AS(T)
|
||||
#endif
|
||||
138
libraries/FastLED/src/fl/allocator.cpp
Normal file
138
libraries/FastLED/src/fl/allocator.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/thread_local.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_system.h"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef ESP32
|
||||
// On esp32, attempt to always allocate in psram first.
|
||||
void *DefaultAlloc(fl::size size) {
|
||||
void *out = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
|
||||
if (out == nullptr) {
|
||||
// Fallback to default allocator.
|
||||
out = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
void DefaultFree(void *ptr) { heap_caps_free(ptr); }
|
||||
#else
|
||||
void *DefaultAlloc(fl::size size) { return malloc(size); }
|
||||
void DefaultFree(void *ptr) { free(ptr); }
|
||||
#endif
|
||||
|
||||
void *(*Alloc)(fl::size) = DefaultAlloc;
|
||||
void (*Dealloc)(void *) = DefaultFree;
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
// Test hook interface pointer
|
||||
MallocFreeHook* gMallocFreeHook = nullptr;
|
||||
|
||||
int& tls_reintrancy_count() {
|
||||
static fl::ThreadLocal<int> enabled;
|
||||
return enabled.access();
|
||||
}
|
||||
|
||||
struct MemoryGuard {
|
||||
int& reintrancy_count;
|
||||
MemoryGuard(): reintrancy_count(tls_reintrancy_count()) {
|
||||
reintrancy_count++;
|
||||
}
|
||||
~MemoryGuard() {
|
||||
reintrancy_count--;
|
||||
}
|
||||
bool enabled() const {
|
||||
return reintrancy_count <= 1;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
void SetMallocFreeHook(MallocFreeHook* hook) {
|
||||
gMallocFreeHook = hook;
|
||||
}
|
||||
|
||||
void ClearMallocFreeHook() {
|
||||
gMallocFreeHook = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) {
|
||||
Alloc = alloc;
|
||||
Dealloc = free;
|
||||
}
|
||||
|
||||
void *PSRamAllocate(fl::size size, bool zero) {
|
||||
|
||||
void *ptr = Alloc(size);
|
||||
if (ptr && zero) {
|
||||
memset(ptr, 0, size);
|
||||
}
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onMalloc(ptr, size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void PSRamDeallocate(void *ptr) {
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
// gMallocFreeHook->onFree(ptr);
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onFree(ptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dealloc(ptr);
|
||||
}
|
||||
|
||||
void* Malloc(fl::size size) {
|
||||
void* ptr = Alloc(size);
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onMalloc(ptr, size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Free(void *ptr) {
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onFree(ptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dealloc(ptr);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
774
libraries/FastLED/src/fl/allocator.h
Normal file
774
libraries/FastLED/src/fl/allocator.h
Normal file
@@ -0,0 +1,774 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/bit_cast.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/bitset.h"
|
||||
|
||||
#ifndef FASTLED_DEFAULT_SLAB_SIZE
|
||||
#define FASTLED_DEFAULT_SLAB_SIZE 8
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Test hooks for malloc/free operations
|
||||
#if defined(FASTLED_TESTING)
|
||||
// Interface class for malloc/free test hooks
|
||||
class MallocFreeHook {
|
||||
public:
|
||||
virtual ~MallocFreeHook() = default;
|
||||
virtual void onMalloc(void* ptr, fl::size size) = 0;
|
||||
virtual void onFree(void* ptr) = 0;
|
||||
};
|
||||
|
||||
// Set test hooks for malloc and free operations
|
||||
void SetMallocFreeHook(MallocFreeHook* hook);
|
||||
|
||||
// Clear test hooks (set to nullptr)
|
||||
void ClearMallocFreeHook();
|
||||
#endif
|
||||
|
||||
void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *));
|
||||
void *PSRamAllocate(fl::size size, bool zero = true);
|
||||
void PSRamDeallocate(void *ptr);
|
||||
void* Malloc(fl::size size);
|
||||
void Free(void *ptr);
|
||||
|
||||
template <typename T> class PSRamAllocator {
|
||||
public:
|
||||
static T *Alloc(fl::size n) {
|
||||
void *ptr = PSRamAllocate(sizeof(T) * n, true);
|
||||
return fl::bit_cast_ptr<T>(ptr);
|
||||
}
|
||||
|
||||
static void Free(T *p) {
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
PSRamDeallocate(p);
|
||||
}
|
||||
};
|
||||
|
||||
// std compatible allocator.
|
||||
template <typename T> class allocator {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator<U>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
template <typename U>
|
||||
allocator(const allocator<U>&) noexcept {}
|
||||
|
||||
// Destructor
|
||||
~allocator() noexcept {}
|
||||
|
||||
// Use this to allocate large blocks of memory for T.
|
||||
// This is useful for large arrays or objects that need to be allocated
|
||||
// in a single block.
|
||||
T* allocate(fl::size n) {
|
||||
if (n == 0) {
|
||||
return nullptr; // Handle zero allocation
|
||||
}
|
||||
fl::size size = sizeof(T) * n;
|
||||
void *ptr = Malloc(size);
|
||||
if (ptr == nullptr) {
|
||||
return nullptr; // Handle allocation failure
|
||||
}
|
||||
fl::memfill(ptr, 0, sizeof(T) * n); // Zero-initialize the memory
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
void deallocate(T* p, fl::size n) {
|
||||
FASTLED_UNUSED(n);
|
||||
if (p == nullptr) {
|
||||
return; // Handle null pointer
|
||||
}
|
||||
Free(p); // Free the allocated memory
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> class allocator_psram {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_psram<U>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_psram() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
template <typename U>
|
||||
allocator_psram(const allocator_psram<U>&) noexcept {}
|
||||
|
||||
// Destructor
|
||||
~allocator_psram() noexcept {}
|
||||
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
return PSRamAllocator<T>::Alloc(n);
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
PSRamAllocator<T>::Free(p);
|
||||
FASTLED_UNUSED(n);
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Slab allocator for fixed-size objects
|
||||
// Optimized for frequent allocation/deallocation of objects of the same size
|
||||
// Uses pre-allocated memory slabs with free lists to reduce fragmentation
|
||||
template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
|
||||
class SlabAllocator {
|
||||
private:
|
||||
|
||||
|
||||
static constexpr fl::size SLAB_BLOCK_SIZE = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
|
||||
static constexpr fl::size BLOCKS_PER_SLAB = SLAB_SIZE;
|
||||
static constexpr fl::size SLAB_MEMORY_SIZE = SLAB_BLOCK_SIZE * BLOCKS_PER_SLAB;
|
||||
|
||||
struct Slab {
|
||||
Slab* next;
|
||||
u8* memory;
|
||||
fl::size allocated_count;
|
||||
fl::bitset_fixed<BLOCKS_PER_SLAB> allocated_blocks; // Track which blocks are allocated
|
||||
|
||||
Slab() : next(nullptr), memory(nullptr), allocated_count(0) {}
|
||||
|
||||
~Slab() {
|
||||
if (memory) {
|
||||
free(memory);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Slab* slabs_;
|
||||
fl::size total_allocated_;
|
||||
fl::size total_deallocated_;
|
||||
|
||||
Slab* createSlab() {
|
||||
Slab* slab = static_cast<Slab*>(malloc(sizeof(Slab)));
|
||||
if (!slab) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use placement new to properly initialize the Slab
|
||||
new(slab) Slab();
|
||||
|
||||
slab->memory = static_cast<u8*>(malloc(SLAB_MEMORY_SIZE));
|
||||
if (!slab->memory) {
|
||||
slab->~Slab();
|
||||
free(slab);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize all blocks in the slab as free
|
||||
slab->allocated_blocks.reset(); // All blocks start as free
|
||||
|
||||
// Add slab to the slab list
|
||||
slab->next = slabs_;
|
||||
slabs_ = slab;
|
||||
|
||||
return slab;
|
||||
}
|
||||
|
||||
void* allocateFromSlab(fl::size n = 1) {
|
||||
// Try to find n contiguous free blocks in existing slabs
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
void* ptr = findContiguousBlocks(slab, n);
|
||||
if (ptr) {
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
// No contiguous blocks found, create new slab if n fits
|
||||
if (n <= BLOCKS_PER_SLAB) {
|
||||
if (!createSlab()) {
|
||||
return nullptr; // Out of memory
|
||||
}
|
||||
|
||||
// Try again with the new slab
|
||||
return findContiguousBlocks(slabs_, n);
|
||||
}
|
||||
|
||||
// Request too large for slab, fall back to malloc
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* findContiguousBlocks(Slab* slab, fl::size n) {
|
||||
// Check if allocation is too large for this slab
|
||||
if (n > BLOCKS_PER_SLAB) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use bitset's find_run to find n contiguous free blocks (false = free)
|
||||
fl::i32 start = slab->allocated_blocks.find_run(false, static_cast<fl::u32>(n));
|
||||
if (start >= 0) {
|
||||
// Mark blocks as allocated
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
slab->allocated_blocks.set(static_cast<fl::u32>(start + i), true);
|
||||
}
|
||||
slab->allocated_count += n;
|
||||
total_allocated_ += n;
|
||||
|
||||
// Return pointer to the first block
|
||||
return slab->memory + static_cast<fl::size>(start) * SLAB_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void deallocateToSlab(void* ptr, fl::size n = 1) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find which slab this block belongs to
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
u8* slab_start = slab->memory;
|
||||
u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
|
||||
u8* block_ptr = fl::bit_cast_ptr<u8>(ptr);
|
||||
|
||||
if (block_ptr >= slab_start && block_ptr < slab_end) {
|
||||
fl::size block_index = (block_ptr - slab_start) / SLAB_BLOCK_SIZE;
|
||||
|
||||
// Mark blocks as free in the bitset
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (block_index + i < BLOCKS_PER_SLAB) {
|
||||
slab->allocated_blocks.set(block_index + i, false);
|
||||
}
|
||||
}
|
||||
|
||||
slab->allocated_count -= n;
|
||||
total_deallocated_ += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SlabAllocator() : slabs_(nullptr), total_allocated_(0), total_deallocated_(0) {}
|
||||
|
||||
// Destructor
|
||||
~SlabAllocator() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Non-copyable
|
||||
SlabAllocator(const SlabAllocator&) = delete;
|
||||
SlabAllocator& operator=(const SlabAllocator&) = delete;
|
||||
|
||||
// Movable
|
||||
SlabAllocator(SlabAllocator&& other) noexcept
|
||||
: slabs_(other.slabs_), total_allocated_(other.total_allocated_), total_deallocated_(other.total_deallocated_) {
|
||||
other.slabs_ = nullptr;
|
||||
other.total_allocated_ = 0;
|
||||
other.total_deallocated_ = 0;
|
||||
}
|
||||
|
||||
SlabAllocator& operator=(SlabAllocator&& other) noexcept {
|
||||
if (this != &other) {
|
||||
cleanup();
|
||||
slabs_ = other.slabs_;
|
||||
total_allocated_ = other.total_allocated_;
|
||||
total_deallocated_ = other.total_deallocated_;
|
||||
other.slabs_ = nullptr;
|
||||
other.total_allocated_ = 0;
|
||||
other.total_deallocated_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* allocate(fl::size n = 1) {
|
||||
if (n == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to allocate from slab first
|
||||
void* ptr = allocateFromSlab(n);
|
||||
if (ptr) {
|
||||
fl::memfill(ptr, 0, sizeof(T) * n);
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
// Fall back to regular malloc for large allocations
|
||||
ptr = malloc(sizeof(T) * n);
|
||||
if (ptr) {
|
||||
fl::memfill(ptr, 0, sizeof(T) * n);
|
||||
}
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
void deallocate(T* ptr, fl::size n = 1) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to deallocate from slab first
|
||||
bool found_in_slab = false;
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
u8* slab_start = slab->memory;
|
||||
u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
|
||||
u8* block_ptr = fl::bit_cast_ptr<u8>(static_cast<void*>(ptr));
|
||||
|
||||
if (block_ptr >= slab_start && block_ptr < slab_end) {
|
||||
deallocateToSlab(ptr, n);
|
||||
found_in_slab = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_in_slab) {
|
||||
// This was allocated with regular malloc
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Get allocation statistics
|
||||
fl::size getTotalAllocated() const { return total_allocated_; }
|
||||
fl::size getTotalDeallocated() const { return total_deallocated_; }
|
||||
fl::size getActiveAllocations() const { return total_allocated_ - total_deallocated_; }
|
||||
|
||||
// Get number of slabs
|
||||
fl::size getSlabCount() const {
|
||||
fl::size count = 0;
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Cleanup all slabs
|
||||
void cleanup() {
|
||||
while (slabs_) {
|
||||
Slab* next = slabs_->next;
|
||||
slabs_->~Slab();
|
||||
free(slabs_);
|
||||
slabs_ = next;
|
||||
}
|
||||
total_allocated_ = 0;
|
||||
total_deallocated_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// STL-compatible slab allocator
|
||||
template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
|
||||
class allocator_slab {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = typename fl::conditional<
|
||||
fl::is_same<U, void>::value,
|
||||
allocator_slab<char, SLAB_SIZE>,
|
||||
allocator_slab<U, SLAB_SIZE>
|
||||
>::type;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_slab() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
allocator_slab(const allocator_slab& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
allocator_slab& operator=(const allocator_slab& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Template copy constructor
|
||||
template <typename U>
|
||||
allocator_slab(const allocator_slab<U, SLAB_SIZE>& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~allocator_slab() noexcept {}
|
||||
|
||||
private:
|
||||
// Get the shared static allocator instance
|
||||
static SlabAllocator<T, SLAB_SIZE>& get_allocator() {
|
||||
static SlabAllocator<T, SLAB_SIZE> allocator;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
public:
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
// Use a static allocator instance per type/size combination
|
||||
SlabAllocator<T, SLAB_SIZE>& allocator = get_allocator();
|
||||
return allocator.allocate(n);
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
// Use the same static allocator instance
|
||||
SlabAllocator<T, SLAB_SIZE>& allocator = get_allocator();
|
||||
allocator.deallocate(p, n);
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
|
||||
// Cleanup method to clean up the static slab allocator
|
||||
void cleanup() {
|
||||
// Access the same static allocator instance and clean it up
|
||||
static SlabAllocator<T, SLAB_SIZE> allocator;
|
||||
allocator.cleanup();
|
||||
}
|
||||
|
||||
// Equality comparison
|
||||
bool operator==(const allocator_slab& other) const noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return true; // All instances are equivalent
|
||||
}
|
||||
|
||||
bool operator!=(const allocator_slab& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Inlined allocator that stores the first N elements inline
|
||||
// Falls back to the base allocator for additional elements
|
||||
template <typename T, fl::size N, typename BaseAllocator = fl::allocator<T>>
|
||||
class allocator_inlined {
|
||||
private:
|
||||
|
||||
// Inlined storage block
|
||||
struct InlinedStorage {
|
||||
alignas(T) u8 data[N * sizeof(T)];
|
||||
|
||||
InlinedStorage() {
|
||||
fl::memfill(data, 0, sizeof(data));
|
||||
}
|
||||
};
|
||||
|
||||
InlinedStorage m_inlined_storage;
|
||||
BaseAllocator m_base_allocator;
|
||||
fl::size m_inlined_used = 0;
|
||||
fl::bitset_fixed<N> m_free_bits; // Track free slots for inlined memory only
|
||||
fl::size m_active_allocations = 0; // Track current active allocations
|
||||
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_inlined<U, N, typename BaseAllocator::template rebind<U>::other>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_inlined() noexcept = default;
|
||||
|
||||
// Copy constructor
|
||||
allocator_inlined(const allocator_inlined& other) noexcept {
|
||||
// Copy inlined data
|
||||
m_inlined_used = other.m_inlined_used;
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
|
||||
}
|
||||
|
||||
// Copy free bits
|
||||
m_free_bits = other.m_free_bits;
|
||||
|
||||
// Note: Heap allocations are not copied, only inlined data
|
||||
|
||||
// Copy active allocations count
|
||||
m_active_allocations = other.m_active_allocations;
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
allocator_inlined& operator=(const allocator_inlined& other) noexcept {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
|
||||
// Copy inlined data
|
||||
m_inlined_used = other.m_inlined_used;
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
|
||||
}
|
||||
|
||||
// Copy free bits
|
||||
m_free_bits = other.m_free_bits;
|
||||
|
||||
// Note: Heap allocations are not copied, only inlined data
|
||||
|
||||
// Copy active allocations count
|
||||
m_active_allocations = other.m_active_allocations;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Template copy constructor
|
||||
template <typename U>
|
||||
allocator_inlined(const allocator_inlined<U, N, typename BaseAllocator::template rebind<U>::other>& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~allocator_inlined() noexcept {
|
||||
clear();
|
||||
}
|
||||
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
if (n == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// For large allocations (n > 1), use base allocator directly
|
||||
if (n > 1) {
|
||||
T* ptr = m_base_allocator.allocate(n);
|
||||
if (ptr) {
|
||||
m_active_allocations += n;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// For single allocations, first try inlined memory
|
||||
// Find first free inlined slot
|
||||
fl::i32 free_slot = m_free_bits.find_first(false);
|
||||
if (free_slot >= 0 && static_cast<fl::size>(free_slot) < N) {
|
||||
// Mark the inlined slot as used
|
||||
m_free_bits.set(static_cast<fl::u32>(free_slot), true);
|
||||
|
||||
// Update inlined usage tracking
|
||||
if (static_cast<fl::size>(free_slot) + 1 > m_inlined_used) {
|
||||
m_inlined_used = static_cast<fl::size>(free_slot) + 1;
|
||||
}
|
||||
m_active_allocations++;
|
||||
return &get_inlined_ptr()[static_cast<fl::size>(free_slot)];
|
||||
}
|
||||
|
||||
// No inlined slots available, use heap allocation
|
||||
T* ptr = m_base_allocator.allocate(1);
|
||||
if (ptr) {
|
||||
m_active_allocations++;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
if (!p || n == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is inlined memory
|
||||
T* inlined_start = get_inlined_ptr();
|
||||
T* inlined_end = inlined_start + N;
|
||||
|
||||
if (p >= inlined_start && p < inlined_end) {
|
||||
// This is inlined memory, mark slots as free
|
||||
fl::size slot_index = (p - inlined_start);
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (slot_index + i < N) {
|
||||
m_free_bits.set(slot_index + i, false); // Mark as free
|
||||
}
|
||||
}
|
||||
m_active_allocations -= n;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fallback to base allocator for heap allocations
|
||||
m_base_allocator.deallocate(p, n);
|
||||
m_active_allocations -= n;
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
|
||||
// Clear all allocated memory
|
||||
void clear() {
|
||||
// Destroy inlined objects
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
get_inlined_ptr()[i].~T();
|
||||
}
|
||||
m_inlined_used = 0;
|
||||
m_free_bits.reset();
|
||||
m_active_allocations = 0;
|
||||
|
||||
// Clean up the base allocator (for SlabAllocator, this clears slabs and free lists)
|
||||
cleanup_base_allocator();
|
||||
}
|
||||
|
||||
// Get total allocated size
|
||||
fl::size total_size() const {
|
||||
return m_active_allocations;
|
||||
}
|
||||
|
||||
// Get inlined capacity
|
||||
fl::size inlined_capacity() const {
|
||||
return N;
|
||||
}
|
||||
|
||||
// Check if using inlined storage
|
||||
bool is_using_inlined() const {
|
||||
return m_active_allocations == m_inlined_used;
|
||||
}
|
||||
|
||||
private:
|
||||
T* get_inlined_ptr() {
|
||||
return reinterpret_cast<T*>(m_inlined_storage.data);
|
||||
}
|
||||
|
||||
const T* get_inlined_ptr() const {
|
||||
return reinterpret_cast<const T*>(m_inlined_storage.data);
|
||||
}
|
||||
|
||||
// SFINAE helper to detect if base allocator has cleanup() method
|
||||
template<typename U>
|
||||
static auto has_cleanup_impl(int) -> decltype(fl::declval<U>().cleanup(), fl::true_type{});
|
||||
|
||||
template<typename U>
|
||||
static fl::false_type has_cleanup_impl(...);
|
||||
|
||||
using has_cleanup = decltype(has_cleanup_impl<BaseAllocator>(0));
|
||||
|
||||
// Call cleanup on base allocator if it has the method
|
||||
void cleanup_base_allocator() {
|
||||
cleanup_base_allocator_impl(has_cleanup{});
|
||||
}
|
||||
|
||||
void cleanup_base_allocator_impl(fl::true_type) {
|
||||
m_base_allocator.cleanup();
|
||||
}
|
||||
|
||||
void cleanup_base_allocator_impl(fl::false_type) {
|
||||
// Base allocator doesn't have cleanup method, do nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Equality comparison
|
||||
bool operator==(const allocator_inlined& other) const noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return true; // All instances are equivalent for now
|
||||
}
|
||||
|
||||
bool operator!=(const allocator_inlined& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Inlined allocator that uses PSRam for heap allocation
|
||||
template <typename T, fl::size N>
|
||||
using allocator_inlined_psram = allocator_inlined<T, N, fl::allocator_psram<T>>;
|
||||
|
||||
// Inlined allocator that uses slab allocator for heap allocation
|
||||
template <typename T, fl::size N, fl::size SLAB_SIZE = 8>
|
||||
using allocator_inlined_slab_psram = allocator_inlined<T, N, fl::allocator_slab<T, SLAB_SIZE>>;
|
||||
|
||||
|
||||
template <typename T, fl::size N>
|
||||
using allocator_inlined_slab = allocator_inlined<T, N, fl::allocator_slab<T>>;
|
||||
|
||||
} // namespace fl
|
||||
201
libraries/FastLED/src/fl/array.h
Normal file
201
libraries/FastLED/src/fl/array.h
Normal file
@@ -0,0 +1,201 @@
|
||||
// allow-include-after-namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/bit_cast.h"
|
||||
|
||||
#include "fl/initializer_list.h"
|
||||
#include "fl/has_include.h"
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
/**
|
||||
* @brief A fixed-size array implementation similar to std::array
|
||||
*
|
||||
* This class provides a thin wrapper around a C-style array with
|
||||
* STL container-like interface.
|
||||
*
|
||||
* @tparam T The type of elements
|
||||
* @tparam N The number of elements
|
||||
*/
|
||||
template <typename T, fl::size N> class array {
|
||||
public:
|
||||
// Standard container type definitions
|
||||
using value_type = T;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
using reference = value_type &;
|
||||
using const_reference = const value_type &;
|
||||
using pointer = value_type *;
|
||||
using const_pointer = const value_type *;
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
|
||||
// Default constructor - elements are default-initialized
|
||||
array() = default;
|
||||
|
||||
// Fill constructor
|
||||
explicit array(const T &value) {
|
||||
// std::fill_n(begin(), N, value);
|
||||
fill_n(data_, N, value);
|
||||
}
|
||||
|
||||
// Initializer list constructor
|
||||
array(fl::initializer_list<T> list) {
|
||||
fl::size i = 0;
|
||||
for (auto it = list.begin(); it != list.end() && i < N; ++it, ++i) {
|
||||
data_[i] = *it;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
array(const array &) = default;
|
||||
|
||||
// Move constructor
|
||||
array(array &&) = default;
|
||||
|
||||
// Copy assignment
|
||||
array &operator=(const array &) = default;
|
||||
|
||||
// Move assignment
|
||||
array &operator=(array &&) = default;
|
||||
|
||||
// Element access
|
||||
T &at(fl::size pos) {
|
||||
if (pos >= N) {
|
||||
return error_value();
|
||||
}
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
const T &at(fl::size pos) const {
|
||||
if (pos >= N) {
|
||||
return error_value();
|
||||
}
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
T &operator[](fl::size pos) { return data_[pos]; }
|
||||
|
||||
const_reference operator[](fl::size pos) const { return data_[pos]; }
|
||||
|
||||
T &front() { return data_[0]; }
|
||||
|
||||
const T &front() const { return data_[0]; }
|
||||
|
||||
T &back() { return data_[N - 1]; }
|
||||
|
||||
const T &back() const { return data_[N - 1]; }
|
||||
|
||||
pointer data() noexcept { return data_; }
|
||||
|
||||
const_pointer data() const noexcept { return data_; }
|
||||
|
||||
// Iterators
|
||||
iterator begin() noexcept { return data_; }
|
||||
|
||||
const_iterator begin() const noexcept { return data_; }
|
||||
|
||||
const_iterator cbegin() const noexcept { return data_; }
|
||||
|
||||
iterator end() noexcept { return data_ + N; }
|
||||
|
||||
const_iterator end() const noexcept { return data_ + N; }
|
||||
|
||||
// Capacity
|
||||
bool empty() const noexcept { return N == 0; }
|
||||
|
||||
fl::size size() const noexcept { return N; }
|
||||
|
||||
fl::size max_size() const noexcept { return N; }
|
||||
|
||||
// Operations
|
||||
void fill(const T &value) {
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
data_[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(array &other) {
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
fl::swap(data_[i], other.data_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static T &error_value() {
|
||||
static T empty_value;
|
||||
return empty_value;
|
||||
}
|
||||
T data_[N];
|
||||
};
|
||||
|
||||
// Non-member functions
|
||||
template <typename T, fl::size N>
|
||||
bool operator==(const array<T, N> &lhs, const array<T, N> &rhs) {
|
||||
// return std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, fl::size N>
|
||||
bool operator!=(const array<T, N> &lhs, const array<T, N> &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename T, fl::size N>
|
||||
void swap(array<T, N> &lhs,
|
||||
array<T, N> &rhs) noexcept(noexcept(lhs.swap(rhs))) {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
// FASTLED_STACK_ARRAY
|
||||
// An array of variable length that is allocated on the stack using
|
||||
// either alloca or a variable length array (VLA) support built into the
|
||||
// the compiler.
|
||||
// Example:
|
||||
// Instead of: int array[buff_size];
|
||||
// You'd use: FASTLED_STACK_ARRAY(int, array, buff_size);
|
||||
|
||||
#ifndef FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
#if defined(__clang__) || defined(ARDUINO_GIGA_M7) || defined(ARDUINO_GIGA)
|
||||
// Clang doesn't have variable length arrays. Therefore we need to emulate them
|
||||
// using alloca. It's been found that Arduino Giga M7 also doesn't support
|
||||
// variable length arrays for some reason so we force it to emulate them as well
|
||||
// in this case.
|
||||
#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 1
|
||||
#else
|
||||
// Else, assume the compiler is gcc, which has variable length arrays
|
||||
#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 0
|
||||
#endif
|
||||
#endif // FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
|
||||
#if !FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE NAME[SIZE]; \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif FL_HAS_INCLUDE(<alloca.h>)
|
||||
#include <alloca.h>
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = fl::bit_cast_ptr<TYPE>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif FL_HAS_INCLUDE(<cstdlib>)
|
||||
#include <cstdlib> // ok include
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = fl::bit_cast_ptr<TYPE>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#else
|
||||
#error "Compiler does not allow variable type arrays."
|
||||
#endif
|
||||
8
libraries/FastLED/src/fl/assert.h
Normal file
8
libraries/FastLED/src/fl/assert.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "platforms/assert_defs.h"
|
||||
|
||||
#ifndef FL_ASSERT
|
||||
#define FL_ASSERT(x, MSG) FASTLED_ASSERT(x, MSG)
|
||||
#define FL_ASSERT_IF FASTLED_ASSERT_IF
|
||||
#endif
|
||||
211
libraries/FastLED/src/fl/async.cpp
Normal file
211
libraries/FastLED/src/fl/async.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "fl/async.h"
|
||||
#include "fl/functional.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/task.h"
|
||||
#include "fl/time.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
// Platform-specific includes
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" void emscripten_sleep(unsigned int ms);
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
AsyncManager& AsyncManager::instance() {
|
||||
return fl::Singleton<AsyncManager>::instance();
|
||||
}
|
||||
|
||||
void AsyncManager::register_runner(async_runner* runner) {
|
||||
if (runner && fl::find(mRunners.begin(), mRunners.end(), runner) == mRunners.end()) {
|
||||
mRunners.push_back(runner);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncManager::unregister_runner(async_runner* runner) {
|
||||
auto it = fl::find(mRunners.begin(), mRunners.end(), runner);
|
||||
if (it != mRunners.end()) {
|
||||
mRunners.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncManager::update_all() {
|
||||
// Update all registered runners
|
||||
for (auto* runner : mRunners) {
|
||||
if (runner) {
|
||||
runner->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncManager::has_active_tasks() const {
|
||||
for (const auto* runner : mRunners) {
|
||||
if (runner && runner->has_active_tasks()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t AsyncManager::total_active_tasks() const {
|
||||
size_t total = 0;
|
||||
for (const auto* runner : mRunners) {
|
||||
if (runner) {
|
||||
total += runner->active_task_count();
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Public API functions
|
||||
|
||||
void async_run() {
|
||||
fl::Scheduler::instance().update();
|
||||
AsyncManager::instance().update_all();
|
||||
}
|
||||
|
||||
void async_yield() {
|
||||
// Always pump all async tasks first
|
||||
async_run();
|
||||
|
||||
// Platform-specific yielding behavior
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// WASM: Use emscripten_sleep to yield control to browser event loop
|
||||
emscripten_sleep(1); // Sleep for 1ms to yield to browser
|
||||
#endif
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
async_run(); // Give other async tasks a chance
|
||||
}
|
||||
}
|
||||
|
||||
size_t async_active_tasks() {
|
||||
return AsyncManager::instance().total_active_tasks();
|
||||
}
|
||||
|
||||
bool async_has_tasks() {
|
||||
return AsyncManager::instance().has_active_tasks();
|
||||
}
|
||||
|
||||
// Scheduler implementation
|
||||
Scheduler& Scheduler::instance() {
|
||||
return fl::Singleton<Scheduler>::instance();
|
||||
}
|
||||
|
||||
int Scheduler::add_task(task t) {
|
||||
if (t.get_impl()) {
|
||||
t.get_impl()->mTaskId = mNextTaskId++;
|
||||
int task_id = t.get_impl()->mTaskId;
|
||||
mTasks.push_back(fl::move(t));
|
||||
return task_id;
|
||||
}
|
||||
return 0; // Invalid task
|
||||
}
|
||||
|
||||
void Scheduler::update() {
|
||||
uint32_t current_time = fl::time();
|
||||
|
||||
// Use index-based iteration to avoid iterator invalidation issues
|
||||
for (fl::size i = 0; i < mTasks.size();) {
|
||||
task& t = mTasks[i];
|
||||
auto impl = t.get_impl();
|
||||
|
||||
if (!impl || impl->is_canceled()) {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else {
|
||||
// Check if task is ready to run (frame tasks will return false here)
|
||||
bool should_run = impl->ready_to_run(current_time);
|
||||
|
||||
if (should_run) {
|
||||
// Update last run time for recurring tasks
|
||||
impl->set_last_run_time(current_time);
|
||||
|
||||
// Execute the task
|
||||
if (impl->has_then()) {
|
||||
impl->execute_then();
|
||||
} else {
|
||||
warn_no_then(impl->id(), impl->trace_label());
|
||||
}
|
||||
|
||||
// Remove one-shot tasks, keep recurring ones
|
||||
bool is_recurring = (impl->type() == TaskType::kEveryMs || impl->type() == TaskType::kAtFramerate);
|
||||
if (is_recurring) {
|
||||
++i; // Keep recurring tasks
|
||||
} else {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
}
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::update_before_frame_tasks() {
|
||||
update_tasks_of_type(TaskType::kBeforeFrame);
|
||||
}
|
||||
|
||||
void Scheduler::update_after_frame_tasks() {
|
||||
update_tasks_of_type(TaskType::kAfterFrame);
|
||||
}
|
||||
|
||||
void Scheduler::update_tasks_of_type(TaskType task_type) {
|
||||
uint32_t current_time = fl::time();
|
||||
|
||||
// Use index-based iteration to avoid iterator invalidation issues
|
||||
for (fl::size i = 0; i < mTasks.size();) {
|
||||
task& t = mTasks[i];
|
||||
auto impl = t.get_impl();
|
||||
|
||||
if (!impl || impl->is_canceled()) {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else if (impl->type() == task_type) {
|
||||
// This is a frame task of the type we're looking for
|
||||
bool should_run = impl->ready_to_run_frame_task(current_time);
|
||||
|
||||
if (should_run) {
|
||||
// Update last run time for frame tasks (though they don't use it)
|
||||
impl->set_last_run_time(current_time);
|
||||
|
||||
// Execute the task
|
||||
if (impl->has_then()) {
|
||||
impl->execute_then();
|
||||
} else {
|
||||
warn_no_then(impl->id(), impl->trace_label());
|
||||
}
|
||||
|
||||
// Frame tasks are always one-shot, so remove them after execution
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
++i; // Not the task type we're looking for
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::warn_no_then(int task_id, const fl::string& trace_label) {
|
||||
if (!trace_label.empty()) {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id << " launched at " << trace_label);
|
||||
} else {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::warn_no_catch(int task_id, const fl::string& trace_label, const Error& error) {
|
||||
if (!trace_label.empty()) {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no catch_() callback set for Task#") << task_id << " launched at " << trace_label << ". Error: " << error.message);
|
||||
} else {
|
||||
FL_WARN(fl::string("[fl/task] Warning: no catch_() callback set for Task#") << task_id << ". Error: " << error.message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
260
libraries/FastLED/src/fl/async.h
Normal file
260
libraries/FastLED/src/fl/async.h
Normal file
@@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
/// @file async.h
|
||||
/// @brief Generic asynchronous task management for FastLED
|
||||
///
|
||||
/// This module provides a unified system for managing asynchronous operations
|
||||
/// across FastLED, including HTTP requests, timers, and other background tasks.
|
||||
///
|
||||
/// The async system integrates with FastLED's engine events and can be pumped
|
||||
/// during delay() calls on WASM platforms for optimal responsiveness.
|
||||
///
|
||||
/// @section Usage
|
||||
/// @code
|
||||
/// #include "fl/async.h"
|
||||
///
|
||||
/// // Create a custom async runner
|
||||
/// class Myasync_runner : public fl::async_runner {
|
||||
/// public:
|
||||
/// void update() override {
|
||||
/// // Process your async tasks here
|
||||
/// process_timers();
|
||||
/// handle_network_events();
|
||||
/// }
|
||||
///
|
||||
/// bool has_active_tasks() const override {
|
||||
/// return !mTimers.empty() || mNetworkActive;
|
||||
/// }
|
||||
///
|
||||
/// size_t active_task_count() const override {
|
||||
/// return mTimers.size() + (mNetworkActive ? 1 : 0);
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// void setup() {
|
||||
/// Myasync_runner* runner = new Myasync_runner();
|
||||
/// fl::AsyncManager::instance().register_runner(runner);
|
||||
///
|
||||
/// // Now your async tasks will be automatically updated during:
|
||||
/// // - FastLED.show() calls (via engine events)
|
||||
/// // - delay() calls on WASM platforms
|
||||
/// // - Manual fl::async_run() calls
|
||||
/// }
|
||||
/// @endcode
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/promise.h"
|
||||
#include "fl/promise_result.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/thread_local.h"
|
||||
|
||||
#include "fl/task.h"
|
||||
#include "fl/time.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class AsyncManager;
|
||||
|
||||
/// @brief Generic asynchronous task runner interface
|
||||
class async_runner {
|
||||
public:
|
||||
virtual ~async_runner() = default;
|
||||
|
||||
/// Update this async runner (called during async pumping)
|
||||
virtual void update() = 0;
|
||||
|
||||
/// Check if this runner has active tasks
|
||||
virtual bool has_active_tasks() const = 0;
|
||||
|
||||
/// Get number of active tasks (for debugging/monitoring)
|
||||
virtual size_t active_task_count() const = 0;
|
||||
};
|
||||
|
||||
/// @brief Async task manager (singleton)
|
||||
class AsyncManager {
|
||||
public:
|
||||
static AsyncManager& instance();
|
||||
|
||||
/// Register an async runner
|
||||
void register_runner(async_runner* runner);
|
||||
|
||||
/// Unregister an async runner
|
||||
void unregister_runner(async_runner* runner);
|
||||
|
||||
/// Update all registered async runners
|
||||
void update_all();
|
||||
|
||||
/// Check if there are any active async tasks
|
||||
bool has_active_tasks() const;
|
||||
|
||||
/// Get total number of active tasks across all runners
|
||||
size_t total_active_tasks() const;
|
||||
|
||||
private:
|
||||
fl::vector<async_runner*> mRunners;
|
||||
};
|
||||
|
||||
/// @brief Platform-specific async yield function
|
||||
///
|
||||
/// This function pumps all async tasks and yields control appropriately for the platform:
|
||||
/// - WASM: calls async_run() then emscripten_sleep(1) to yield to browser
|
||||
/// - Other platforms: calls async_run() multiple times with simple yielding
|
||||
///
|
||||
/// This centralizes platform-specific async behavior instead of having #ifdef in generic code.
|
||||
void async_yield();
|
||||
|
||||
|
||||
/// @brief Run all registered async tasks once
|
||||
///
|
||||
/// This function updates all registered async runners (fetch, timers, etc.)
|
||||
/// and is automatically called during:
|
||||
/// - FastLED engine events (onEndFrame)
|
||||
/// - delay() calls on WASM platforms (every 1ms)
|
||||
/// - Manual calls for custom async pumping
|
||||
///
|
||||
/// @note This replaces the old fetch_update() function with a generic approach
|
||||
void async_run();
|
||||
|
||||
|
||||
|
||||
/// @brief Get the number of active async tasks across all systems
|
||||
/// @return Total number of active async tasks
|
||||
size_t async_active_tasks();
|
||||
|
||||
/// @brief Check if any async systems have active tasks
|
||||
/// @return True if any async tasks are running
|
||||
bool async_has_tasks();
|
||||
|
||||
/// @brief Synchronously wait for a promise to complete (ONLY safe in top-level contexts)
|
||||
/// @tparam T The type of value the promise resolves to (automatically deduced)
|
||||
/// @param promise The promise to wait for
|
||||
/// @return A PromiseResult containing either the resolved value T or an Error
|
||||
///
|
||||
/// This function blocks until the promise is either resolved or rejected,
|
||||
/// then returns a PromiseResult that can be checked with ok() for success/failure.
|
||||
/// While waiting, it continuously calls async_yield() to pump async tasks and yield appropriately.
|
||||
///
|
||||
/// **SAFETY WARNING**: This function should ONLY be called from top-level contexts
|
||||
/// like Arduino loop() function. Never call this from:
|
||||
/// - Promise callbacks (.then, .catch_)
|
||||
/// - Nested async operations
|
||||
/// - Interrupt handlers
|
||||
/// - Library initialization code
|
||||
///
|
||||
/// The "_top_level" suffix emphasizes this safety requirement.
|
||||
///
|
||||
/// **Type Deduction**: The template parameter T is automatically deduced from the
|
||||
/// promise parameter, so you don't need to specify it explicitly.
|
||||
///
|
||||
/// @section Usage
|
||||
/// @code
|
||||
/// auto promise = fl::fetch_get("http://example.com");
|
||||
/// auto result = fl::await_top_level(promise); // Type automatically deduced!
|
||||
///
|
||||
/// if (result.ok()) {
|
||||
/// const Response& resp = result.value();
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// } else {
|
||||
/// FL_WARN("Error: " << result.error().message);
|
||||
/// }
|
||||
///
|
||||
/// // Or use operator bool
|
||||
/// if (result) {
|
||||
/// FL_WARN("Got response: " << result.value().status());
|
||||
/// }
|
||||
///
|
||||
/// // You can still specify the type explicitly if needed:
|
||||
/// auto explicit_result = fl::await_top_level<response>(promise);
|
||||
/// @endcode
|
||||
template<typename T>
|
||||
fl::result<T> await_top_level(fl::promise<T> promise) {
|
||||
// Handle invalid promises
|
||||
if (!promise.valid()) {
|
||||
return fl::result<T>(Error("Invalid promise"));
|
||||
}
|
||||
|
||||
// If already completed, return immediately
|
||||
if (promise.is_completed()) {
|
||||
if (promise.is_resolved()) {
|
||||
return fl::result<T>(promise.value());
|
||||
} else {
|
||||
return fl::result<T>(promise.error());
|
||||
}
|
||||
}
|
||||
|
||||
// Track recursion depth to prevent infinite loops
|
||||
static fl::ThreadLocal<int> await_depth(0);
|
||||
if (await_depth.access() > 10) {
|
||||
return fl::result<T>(Error("await_top_level recursion limit exceeded - possible infinite loop"));
|
||||
}
|
||||
|
||||
++await_depth.access();
|
||||
|
||||
// Wait for promise to complete while pumping async tasks
|
||||
int pump_count = 0;
|
||||
const int max_pump_iterations = 10000; // Safety limit
|
||||
|
||||
while (!promise.is_completed() && pump_count < max_pump_iterations) {
|
||||
// Update the promise first (in case it's not managed by async system)
|
||||
promise.update();
|
||||
|
||||
// Check if completed after update
|
||||
if (promise.is_completed()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Platform-agnostic async pump and yield
|
||||
async_yield();
|
||||
|
||||
++pump_count;
|
||||
}
|
||||
|
||||
--await_depth.access();
|
||||
|
||||
// Check for timeout
|
||||
if (pump_count >= max_pump_iterations) {
|
||||
return fl::result<T>(Error("await_top_level timeout - promise did not complete"));
|
||||
}
|
||||
|
||||
// Return the result
|
||||
if (promise.is_resolved()) {
|
||||
return fl::result<T>(promise.value());
|
||||
} else {
|
||||
return fl::result<T>(promise.error());
|
||||
}
|
||||
}
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
static Scheduler& instance();
|
||||
|
||||
int add_task(task t);
|
||||
void update();
|
||||
|
||||
// New methods for frame task handling
|
||||
void update_before_frame_tasks();
|
||||
void update_after_frame_tasks();
|
||||
|
||||
// For testing: clear all tasks
|
||||
void clear_all_tasks() { mTasks.clear(); mNextTaskId = 1; }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<Scheduler>;
|
||||
Scheduler() : mTasks() {}
|
||||
|
||||
void warn_no_then(int task_id, const fl::string& trace_label);
|
||||
void warn_no_catch(int task_id, const fl::string& trace_label, const Error& error);
|
||||
|
||||
// Helper method for running specific task types
|
||||
void update_tasks_of_type(TaskType task_type);
|
||||
|
||||
fl::vector<task> mTasks;
|
||||
int mNextTaskId = 1;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
160
libraries/FastLED/src/fl/atomic.h
Normal file
160
libraries/FastLED/src/fl/atomic.h
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/thread.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/align.h"
|
||||
|
||||
#if FASTLED_MULTITHREADED
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
|
||||
#if FASTLED_MULTITHREADED
|
||||
template <typename T>
|
||||
using atomic = std::atomic<T>;
|
||||
#else
|
||||
template <typename T> class AtomicFake;
|
||||
template <typename T>
|
||||
using atomic = AtomicFake<T>;
|
||||
#endif
|
||||
|
||||
using atomic_bool = atomic<bool>;
|
||||
using atomic_int = atomic<int>;
|
||||
using atomic_uint = atomic<unsigned int>;
|
||||
using atomic_u32 = atomic<fl::u32>;
|
||||
using atomic_i32 = atomic<fl::i32>;
|
||||
|
||||
///////////////////// IMPLEMENTATION //////////////////////////////////////
|
||||
|
||||
template <typename T> class AtomicFake {
|
||||
public:
|
||||
AtomicFake() : mValue{} {}
|
||||
explicit AtomicFake(T value) : mValue(value) {}
|
||||
|
||||
// Non-copyable and non-movable
|
||||
AtomicFake(const AtomicFake&) = delete;
|
||||
AtomicFake& operator=(const AtomicFake&) = delete;
|
||||
AtomicFake(AtomicFake&&) = delete;
|
||||
AtomicFake& operator=(AtomicFake&&) = delete;
|
||||
|
||||
// Basic atomic operations - fake implementation (not actually atomic)
|
||||
T load() const {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
void store(T value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
T exchange(T value) {
|
||||
T old = mValue;
|
||||
mValue = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
bool compare_exchange_weak(T& expected, T desired) {
|
||||
if (mValue == expected) {
|
||||
mValue = desired;
|
||||
return true;
|
||||
} else {
|
||||
expected = mValue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool compare_exchange_strong(T& expected, T desired) {
|
||||
return compare_exchange_weak(expected, desired);
|
||||
}
|
||||
|
||||
// Assignment operator
|
||||
T operator=(T value) {
|
||||
store(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Conversion operator
|
||||
operator T() const {
|
||||
return load();
|
||||
}
|
||||
|
||||
// Arithmetic operators (for integral and floating point types)
|
||||
T operator++() {
|
||||
return ++mValue;
|
||||
}
|
||||
|
||||
T operator++(int) {
|
||||
return mValue++;
|
||||
}
|
||||
|
||||
T operator--() {
|
||||
return --mValue;
|
||||
}
|
||||
|
||||
T operator--(int) {
|
||||
return mValue--;
|
||||
}
|
||||
|
||||
T operator+=(T value) {
|
||||
mValue += value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator-=(T value) {
|
||||
mValue -= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator&=(T value) {
|
||||
mValue &= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator|=(T value) {
|
||||
mValue |= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator^=(T value) {
|
||||
mValue ^= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
// Fetch operations
|
||||
T fetch_add(T value) {
|
||||
T old = mValue;
|
||||
mValue += value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_sub(T value) {
|
||||
T old = mValue;
|
||||
mValue -= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_and(T value) {
|
||||
T old = mValue;
|
||||
mValue &= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_or(T value) {
|
||||
T old = mValue;
|
||||
mValue |= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_xor(T value) {
|
||||
T old = mValue;
|
||||
mValue ^= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
private:
|
||||
FL_ALIGN_AS(T) T mValue;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
194
libraries/FastLED/src/fl/audio.cpp
Normal file
194
libraries/FastLED/src/fl/audio.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
|
||||
#include "audio.h"
|
||||
#include "fl/thread_local.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/mutex.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
FFT &get_flex_fft() {
|
||||
static ThreadLocal<FFT> gFlexFFT;
|
||||
return gFlexFFT.access();
|
||||
}
|
||||
|
||||
// Object pool implementation
|
||||
|
||||
struct AudioSamplePool {
|
||||
static AudioSamplePool& instance() {
|
||||
static AudioSamplePool s_pool;
|
||||
return s_pool;
|
||||
}
|
||||
void put(AudioSampleImplPtr&& impl) {
|
||||
if (impl.unique()) {
|
||||
// There is no more shared_ptr to this object, so we can recycle it.
|
||||
fl::lock_guard<fl::mutex> lock(mutex);
|
||||
if (impl && pool.size() < MAX_POOL_SIZE) {
|
||||
// Reset the impl for reuse (clear internal state)
|
||||
impl->reset();
|
||||
pool.push_back(impl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Pool is full, discard the impl
|
||||
impl.reset();
|
||||
}
|
||||
AudioSampleImplPtr getOrCreate() {
|
||||
{
|
||||
fl::lock_guard<fl::mutex> lock(mutex);
|
||||
if (!pool.empty()) {
|
||||
AudioSampleImplPtr impl = pool.back();
|
||||
pool.pop_back();
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
return fl::make_shared<AudioSampleImpl>();
|
||||
}
|
||||
|
||||
fl::vector<AudioSampleImplPtr> pool;
|
||||
static constexpr fl::size MAX_POOL_SIZE = 8;
|
||||
fl::mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
AudioSample::~AudioSample() {
|
||||
if (mImpl) {
|
||||
AudioSamplePool::instance().put(fl::move(mImpl));
|
||||
}
|
||||
}
|
||||
|
||||
const AudioSample::VectorPCM &AudioSample::pcm() const {
|
||||
if (isValid()) {
|
||||
return mImpl->pcm();
|
||||
}
|
||||
static VectorPCM empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
AudioSample &AudioSample::operator=(const AudioSample &other) {
|
||||
mImpl = other.mImpl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
fl::size AudioSample::size() const {
|
||||
if (isValid()) {
|
||||
return mImpl->pcm().size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const fl::i16 &AudioSample::at(fl::size i) const {
|
||||
if (i < size()) {
|
||||
return pcm()[i];
|
||||
}
|
||||
return empty()[0];
|
||||
}
|
||||
|
||||
const fl::i16 &AudioSample::operator[](fl::size i) const { return at(i); }
|
||||
|
||||
bool AudioSample::operator==(const AudioSample &other) const {
|
||||
if (mImpl == other.mImpl) {
|
||||
return true;
|
||||
}
|
||||
if (mImpl == nullptr || other.mImpl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (mImpl->pcm().size() != other.mImpl->pcm().size()) {
|
||||
return false;
|
||||
}
|
||||
for (fl::size i = 0; i < mImpl->pcm().size(); ++i) {
|
||||
if (mImpl->pcm()[i] != other.mImpl->pcm()[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioSample::operator!=(const AudioSample &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
const AudioSample::VectorPCM &AudioSample::empty() {
|
||||
static fl::i16 empty_data[1] = {0};
|
||||
static VectorPCM empty(empty_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
float AudioSample::zcf() const { return mImpl->zcf(); }
|
||||
|
||||
fl::u32 AudioSample::timestamp() const {
|
||||
if (isValid()) {
|
||||
return mImpl->timestamp();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float AudioSample::rms() const {
|
||||
if (!isValid()) {
|
||||
return 0.0f;
|
||||
}
|
||||
fl::u64 sum_sq = 0;
|
||||
const int N = size();
|
||||
for (int i = 0; i < N; ++i) {
|
||||
fl::i32 x32 = fl::i32(pcm()[i]);
|
||||
sum_sq += x32 * x32;
|
||||
}
|
||||
float rms = sqrtf(float(sum_sq) / N);
|
||||
return rms;
|
||||
}
|
||||
|
||||
SoundLevelMeter::SoundLevelMeter(double spl_floor, double smoothing_alpha)
|
||||
: spl_floor_(spl_floor), smoothing_alpha_(smoothing_alpha),
|
||||
dbfs_floor_global_(INFINITY_DOUBLE), offset_(0.0), current_dbfs_(0.0),
|
||||
current_spl_(spl_floor) {}
|
||||
|
||||
void SoundLevelMeter::processBlock(const fl::i16 *samples, fl::size count) {
|
||||
// 1) compute block power → dBFS
|
||||
double sum_sq = 0.0;
|
||||
for (fl::size i = 0; i < count; ++i) {
|
||||
double s = samples[i] / 32768.0; // normalize to ±1
|
||||
sum_sq += s * s;
|
||||
}
|
||||
double p = sum_sq / count; // mean power
|
||||
double dbfs = 10.0 * log10(p + 1e-12);
|
||||
current_dbfs_ = dbfs;
|
||||
|
||||
// 2) update global floor (with optional smoothing)
|
||||
if (dbfs < dbfs_floor_global_) {
|
||||
if (smoothing_alpha_ <= 0.0) {
|
||||
dbfs_floor_global_ = dbfs;
|
||||
} else {
|
||||
dbfs_floor_global_ = smoothing_alpha_ * dbfs +
|
||||
(1.0 - smoothing_alpha_) * dbfs_floor_global_;
|
||||
}
|
||||
offset_ = spl_floor_ - dbfs_floor_global_;
|
||||
}
|
||||
|
||||
// 3) estimate SPL
|
||||
current_spl_ = dbfs + offset_;
|
||||
}
|
||||
|
||||
void AudioSample::fft(FFTBins *out) const {
|
||||
fl::span<const fl::i16> sample = pcm();
|
||||
FFT_Args args;
|
||||
args.samples = sample.size();
|
||||
args.bands = out->size();
|
||||
args.fmin = FFT_Args::DefaultMinFrequency();
|
||||
args.fmax = FFT_Args::DefaultMaxFrequency();
|
||||
args.sample_rate =
|
||||
FFT_Args::DefaultSampleRate(); // TODO: get sample rate from AudioSample
|
||||
get_flex_fft().run(sample, out, args);
|
||||
}
|
||||
|
||||
|
||||
AudioSample::AudioSample(fl::span<const fl::i16> span, fl::u32 timestamp) {
|
||||
mImpl = AudioSamplePool::instance().getOrCreate();
|
||||
auto begin = span.data();
|
||||
auto end = begin + span.size();
|
||||
mImpl->assign(begin, end, timestamp);
|
||||
}
|
||||
|
||||
|
||||
} // namespace fl
|
||||
169
libraries/FastLED/src/fl/audio.h
Normal file
169
libraries/FastLED/src/fl/audio.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/int.h"
|
||||
#include <math.h>
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/span.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class AudioSampleImpl;
|
||||
|
||||
FASTLED_SMART_PTR(AudioSampleImpl);
|
||||
|
||||
// AudioSample is a wrapper around AudioSampleImpl, hiding the reference
|
||||
// counting so that the api object can be simple and have standard object
|
||||
// semantics.
|
||||
class AudioSample {
|
||||
public:
|
||||
using VectorPCM = fl::vector<fl::i16>;
|
||||
using const_iterator = VectorPCM::const_iterator;
|
||||
AudioSample() {}
|
||||
AudioSample(const AudioSample &other) : mImpl(other.mImpl) {}
|
||||
AudioSample(AudioSampleImplPtr impl) : mImpl(impl) {}
|
||||
~AudioSample();
|
||||
|
||||
// Constructor that takes raw audio data and handles pooling internally
|
||||
AudioSample(fl::span<const fl::i16> span, fl::u32 timestamp = 0);
|
||||
|
||||
|
||||
AudioSample &operator=(const AudioSample &other);
|
||||
bool isValid() const { return mImpl != nullptr; }
|
||||
|
||||
fl::size size() const;
|
||||
// Raw pcm levels.
|
||||
const VectorPCM &pcm() const;
|
||||
// Zero crossing factor between 0.0f -> 1.0f, detects "hiss"
|
||||
// and sounds like cloths rubbing. Useful for sound analysis.
|
||||
float zcf() const;
|
||||
float rms() const;
|
||||
fl::u32 timestamp() const; // Timestamp when sample became valid (millis)
|
||||
|
||||
void fft(FFTBins *out) const;
|
||||
|
||||
const_iterator begin() const { return pcm().begin(); }
|
||||
const_iterator end() const { return pcm().end(); }
|
||||
const fl::i16 &at(fl::size i) const;
|
||||
const fl::i16 &operator[](fl::size i) const;
|
||||
operator bool() const { return isValid(); }
|
||||
bool operator==(const AudioSample &other) const;
|
||||
bool operator!=(const AudioSample &other) const;
|
||||
|
||||
private:
|
||||
static const VectorPCM &empty();
|
||||
AudioSampleImplPtr mImpl;
|
||||
};
|
||||
|
||||
// Sound level meter is a persistant measuring class that will auto-tune the
|
||||
// microphone to real world SPL levels. It will adapt to the noise floor of the
|
||||
// environment. Note that the microphone only ever outputs DBFS (dB Full Scale)
|
||||
// values, which are collected over a stream of samples. The sound level meter
|
||||
// will convert this to SPL (Sound Pressure Level) values, which are the real
|
||||
// world values.
|
||||
class SoundLevelMeter {
|
||||
public:
|
||||
/// @param spl_floor The SPL (dB SPL) that corresponds to your true
|
||||
/// noise-floor.
|
||||
/// @param smoothing_alpha [0…1] how quickly to adapt floor; 0=instant min.
|
||||
SoundLevelMeter(double spl_floor = 33.0, double smoothing_alpha = 0.0);
|
||||
|
||||
/// Process a block of int16 PCM samples.
|
||||
void processBlock(const fl::i16 *samples, fl::size count);
|
||||
void processBlock(fl::span<const fl::i16> samples) {
|
||||
processBlock(samples.data(), samples.size());
|
||||
}
|
||||
|
||||
/// @returns most recent block’s level in dBFS (≤ 0)
|
||||
double getDBFS() const { return current_dbfs_; }
|
||||
|
||||
/// @returns calibrated estimate in dB SPL
|
||||
double getSPL() const { return current_spl_; }
|
||||
|
||||
/// change your known noise-floor SPL at runtime
|
||||
void setFloorSPL(double spl_floor) {
|
||||
spl_floor_ = spl_floor;
|
||||
offset_ = spl_floor_ - dbfs_floor_global_;
|
||||
}
|
||||
|
||||
/// reset so the next quiet block will re-initialize your floor
|
||||
void resetFloor() {
|
||||
dbfs_floor_global_ = INFINITY_DOUBLE; // infinity<double>
|
||||
offset_ = 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
double spl_floor_; // e.g. 33.0 dB SPL
|
||||
double smoothing_alpha_; // 0 = pure min, >0 = slow adapt
|
||||
double dbfs_floor_global_; // lowest dBFS seen so far
|
||||
double offset_; // spl_floor_ − dbfs_floor_global_
|
||||
double current_dbfs_; // last block’s dBFS
|
||||
double current_spl_; // last block’s estimated SPL
|
||||
};
|
||||
|
||||
// Implementation details.
|
||||
class AudioSampleImpl {
|
||||
public:
|
||||
using VectorPCM = fl::vector<fl::i16>;
|
||||
~AudioSampleImpl() {}
|
||||
// template <typename It> void assign(It begin, It end) {
|
||||
// assign(begin, end, 0); // Default timestamp to 0
|
||||
// }
|
||||
template <typename It> void assign(It begin, It end, fl::u32 timestamp) {
|
||||
mSignedPcm.assign(begin, end);
|
||||
mTimestamp = timestamp;
|
||||
// calculate zero crossings
|
||||
initZeroCrossings();
|
||||
}
|
||||
const VectorPCM &pcm() const { return mSignedPcm; }
|
||||
fl::u32 timestamp() const { return mTimestamp; }
|
||||
|
||||
// For object pool - reset internal state for reuse
|
||||
void reset() {
|
||||
mSignedPcm.clear();
|
||||
mZeroCrossings = 0;
|
||||
mTimestamp = 0;
|
||||
}
|
||||
|
||||
// "Zero crossing factor". High values > .4 indicate hissing
|
||||
// sounds. For example a microphone rubbing against a clothing.
|
||||
// These types of signals indicate the audio should be ignored.
|
||||
// Low zero crossing factors (with loud sound) indicate that there
|
||||
// is organized sound like that coming from music. This is so cheap
|
||||
// to calculate it's done automatically. It should be one of the first
|
||||
// signals to reject or accept a sound signal.
|
||||
//
|
||||
// Returns: a value -> [0.0f, 1.0f)
|
||||
float zcf() const {
|
||||
const fl::size n = pcm().size();
|
||||
if (n < 2) {
|
||||
return 0.f;
|
||||
}
|
||||
return float(mZeroCrossings) / static_cast<float>(n - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
void initZeroCrossings() {
|
||||
mZeroCrossings = 0;
|
||||
if (mSignedPcm.size() > 1) {
|
||||
for (fl::size i = 1; i < mSignedPcm.size(); ++i) {
|
||||
const bool crossed =
|
||||
(mSignedPcm[i - 1] < 0 && mSignedPcm[i] >= 0) ||
|
||||
(mSignedPcm[i - 1] >= 0 && mSignedPcm[i] < 0);
|
||||
if (crossed) {
|
||||
++mZeroCrossings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VectorPCM mSignedPcm;
|
||||
fl::i16 mZeroCrossings = 0;
|
||||
fl::u32 mTimestamp = 0;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
74
libraries/FastLED/src/fl/audio_input.cpp
Normal file
74
libraries/FastLED/src/fl/audio_input.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
|
||||
|
||||
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/has_include.h"
|
||||
#include "platforms/audio_input_null.hpp"
|
||||
|
||||
|
||||
// Auto-determine Arduino usage if not explicitly set. Force this to 1
|
||||
// if you want to test Arduino path for audio input on a platform with
|
||||
// native audio support.
|
||||
#ifndef FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#if defined(ESP32) && !defined(ESP8266)
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 0
|
||||
#elif FL_HAS_INCLUDE(<Arduino.h>)
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 1
|
||||
#else
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#if defined(ESP32) && !defined(ESP8266)
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 1
|
||||
#else
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 0
|
||||
#endif
|
||||
#else
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 0
|
||||
#endif
|
||||
|
||||
|
||||
// Include ESP32 audio input implementation if on ESP32
|
||||
#if FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#include "platforms/arduino/audio_input.hpp"
|
||||
#elif FASTLED_USES_ESP32_AUDIO_INPUT
|
||||
#include "platforms/esp/32/audio/audio_impl.hpp"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
#if FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
// Use Arduino audio implementation
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
return arduino_create_audio_input(config, error_message);
|
||||
}
|
||||
#elif FASTLED_USES_ESP32_AUDIO_INPUT
|
||||
// ESP32 native implementation
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
return esp32_create_audio_input(config, error_message);
|
||||
}
|
||||
#else
|
||||
// Weak default implementation - no audio support
|
||||
FL_LINK_WEAK
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
if (error_message) {
|
||||
*error_message = "AudioInput not supported on this platform.";
|
||||
}
|
||||
return fl::make_shared<fl::Null_Audio>();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Static method delegates to free function
|
||||
fl::shared_ptr<IAudioInput>
|
||||
IAudioInput::create(const AudioConfig &config, fl::string *error_message) {
|
||||
return platform_create_audio_input(config, error_message);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
142
libraries/FastLED/src/fl/audio_input.h
Normal file
142
libraries/FastLED/src/fl/audio_input.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "platforms/audio.h"
|
||||
|
||||
#ifndef FASTLED_HAS_AUDIO_INPUT
|
||||
#error "platforms/audio.h must define FASTLED_HAS_AUDIO_INPUT"
|
||||
#endif
|
||||
|
||||
|
||||
#define I2S_AUDIO_BUFFER_LEN 512
|
||||
#define AUDIO_DEFAULT_SAMPLE_RATE 44100ul
|
||||
#define AUDIO_DEFAULT_BIT_RESOLUTION 16
|
||||
#define AUDIO_DMA_BUFFER_COUNT 8
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Note: Right now these are esp specific, but they are designed to migrate to a common api.
|
||||
|
||||
enum AudioChannel {
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
Both = 2, // Two microphones can be used to capture both channels with one AudioSource.
|
||||
};
|
||||
|
||||
|
||||
enum I2SCommFormat {
|
||||
Philips = 0X01, // I2S communication I2S Philips standard, data launch at second BCK
|
||||
MSB = 0X02, // I2S communication MSB alignment standard, data launch at first BCK
|
||||
PCMShort = 0x04, // PCM Short standard, also known as DSP mode. The period of synchronization signal (WS) is 1 bck cycle.
|
||||
PCMLong = 0x0C, // PCM Long standard. The period of synchronization signal (WS) is channel_bit*bck cycles.
|
||||
Max = 0x0F, // standard max
|
||||
};
|
||||
|
||||
struct AudioConfigI2S {
|
||||
int mPinWs;
|
||||
int mPinSd;
|
||||
int mPinClk;
|
||||
int mI2sNum;
|
||||
AudioChannel mAudioChannel;
|
||||
u16 mSampleRate;
|
||||
u8 mBitResolution;
|
||||
I2SCommFormat mCommFormat;
|
||||
bool mInvert;
|
||||
AudioConfigI2S(
|
||||
int pin_ws,
|
||||
int pin_sd,
|
||||
int pin_clk,
|
||||
int i2s_num,
|
||||
AudioChannel mic_channel,
|
||||
u16 sample_rate,
|
||||
u8 bit_resolution,
|
||||
I2SCommFormat comm_format = Philips,
|
||||
bool invert = false
|
||||
)
|
||||
: mPinWs(pin_ws), mPinSd(pin_sd), mPinClk(pin_clk),
|
||||
mI2sNum(i2s_num), mAudioChannel(mic_channel),
|
||||
mSampleRate(sample_rate), mBitResolution(bit_resolution), mCommFormat(comm_format), mInvert(invert) {}
|
||||
};
|
||||
|
||||
struct AudioConfigPdm {
|
||||
int mPinDin;
|
||||
int mPinClk;
|
||||
int mI2sNum;
|
||||
u16 mSampleRate;
|
||||
bool mInvert = false;
|
||||
|
||||
AudioConfigPdm(int pin_din, int pin_clk, int i2s_num, u16 sample_rate = AUDIO_DEFAULT_SAMPLE_RATE, bool invert = false)
|
||||
: mPinDin(pin_din), mPinClk(pin_clk), mI2sNum(i2s_num), mSampleRate(sample_rate), mInvert(invert) {}
|
||||
};
|
||||
|
||||
|
||||
class AudioConfig : public fl::Variant<AudioConfigI2S, AudioConfigPdm> {
|
||||
public:
|
||||
// The most common microphone on Amazon as of 2025-September.
|
||||
static AudioConfig CreateInmp441(int pin_ws, int pin_sd, int pin_clk, AudioChannel channel, u16 sample_rate = 44100ul, int i2s_num = 0) {
|
||||
AudioConfigI2S config(pin_ws, pin_sd, pin_clk, i2s_num, channel, sample_rate, 16);
|
||||
return AudioConfig(config);
|
||||
}
|
||||
AudioConfig(const AudioConfigI2S& config) : fl::Variant<AudioConfigI2S, AudioConfigPdm>(config) {}
|
||||
AudioConfig(const AudioConfigPdm& config) : fl::Variant<AudioConfigI2S, AudioConfigPdm>(config) {}
|
||||
};
|
||||
|
||||
class IAudioInput {
|
||||
public:
|
||||
// This is the single factory function for creating the audio source. If the creation was successful, then
|
||||
// the return value will be non-null. If the creation was not successful, then the return value will be null
|
||||
// and the error_message will be set to a non-empty string.
|
||||
// Keep in mind that the AudioConfig is a variant type. Many esp types do not support all the types in the variant.
|
||||
// For example, the AudioConfigPdm is not supported on the ESP32-C3 and in this case it will return a null pointer
|
||||
// and the error_message will be set to a non-empty string.
|
||||
// Implimentation notes:
|
||||
// It's very important that the implimentation uses a esp task / interrupt to fill in the buffer. The reason is that
|
||||
// there will be looooong delays during FastLED show() on some esp platforms, for example idf 4.4. If we do
|
||||
// poll only, then audio buffers can be dropped. However if using a task then the audio buffers will be
|
||||
// set internally via an interrupt / queue and then they can just be popped off the queue.
|
||||
static fl::shared_ptr<IAudioInput> create(const AudioConfig& config, fl::string* error_message = nullptr);
|
||||
|
||||
|
||||
virtual ~IAudioInput() = default;
|
||||
// Starts the audio source.
|
||||
virtual void start() = 0;
|
||||
// Stops the audio source, call this before light sleep.
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual bool error(fl::string* msg = nullptr) = 0; // if an error occured then query it here.
|
||||
// Read audio data and return as AudioSample with calculated timestamp.
|
||||
// Returns invalid AudioSample on error or when no data is available.
|
||||
virtual AudioSample read() = 0;
|
||||
|
||||
// Read all available audio data and return as AudioSample. All AudioSamples
|
||||
// returned by this will be valid.
|
||||
size_t readAll(fl::vector_inlined<AudioSample, 16> *out) {
|
||||
size_t count = 0;
|
||||
while (true) {
|
||||
AudioSample sample = read();
|
||||
if (sample.isValid()) {
|
||||
out->push_back(sample);
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Free function for audio input creation - can be overridden by platform-specific implementations
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig& config, fl::string* error_message = nullptr) FL_LINK_WEAK;
|
||||
|
||||
} // namespace fl
|
||||
759
libraries/FastLED/src/fl/audio_reactive.cpp
Normal file
759
libraries/FastLED/src/fl/audio_reactive.cpp
Normal file
@@ -0,0 +1,759 @@
|
||||
#include "fl/audio_reactive.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/memory.h"
|
||||
#include <math.h>
|
||||
|
||||
namespace fl {
|
||||
|
||||
AudioReactive::AudioReactive()
|
||||
: mConfig{}, mFFTBins(16) // Initialize with 16 frequency bins
|
||||
{
|
||||
// Initialize enhanced beat detection components
|
||||
mSpectralFluxDetector = fl::make_unique<SpectralFluxDetector>();
|
||||
mPerceptualWeighting = fl::make_unique<PerceptualWeighting>();
|
||||
|
||||
// Initialize previous magnitudes array to zero
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
AudioReactive::~AudioReactive() = default;
|
||||
|
||||
void AudioReactive::begin(const AudioReactiveConfig& config) {
|
||||
setConfig(config);
|
||||
|
||||
// Reset state
|
||||
mCurrentData = AudioData{};
|
||||
mSmoothedData = AudioData{};
|
||||
mLastBeatTime = 0;
|
||||
mPreviousVolume = 0.0f;
|
||||
mAGCMultiplier = 1.0f;
|
||||
mMaxSample = 0.0f;
|
||||
mAverageLevel = 0.0f;
|
||||
|
||||
// Reset enhanced beat detection components
|
||||
if (mSpectralFluxDetector) {
|
||||
mSpectralFluxDetector->reset();
|
||||
mSpectralFluxDetector->setThreshold(config.spectralFluxThreshold);
|
||||
}
|
||||
|
||||
// Reset previous magnitudes
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::setConfig(const AudioReactiveConfig& config) {
|
||||
mConfig = config;
|
||||
}
|
||||
|
||||
void AudioReactive::processSample(const AudioSample& sample) {
|
||||
if (!sample.isValid()) {
|
||||
return; // Invalid sample, ignore
|
||||
}
|
||||
|
||||
// Extract timestamp from the AudioSample
|
||||
fl::u32 currentTimeMs = sample.timestamp();
|
||||
|
||||
// Process the AudioSample immediately - timing is gated by sample availability
|
||||
processFFT(sample);
|
||||
updateVolumeAndPeak(sample);
|
||||
|
||||
// Enhanced processing pipeline
|
||||
calculateBandEnergies();
|
||||
updateSpectralFlux();
|
||||
|
||||
// Enhanced beat detection (includes original)
|
||||
detectBeat(currentTimeMs);
|
||||
detectEnhancedBeats(currentTimeMs);
|
||||
|
||||
// Apply perceptual weighting if enabled
|
||||
applyPerceptualWeighting();
|
||||
|
||||
applyGain();
|
||||
applyScaling();
|
||||
smoothResults();
|
||||
|
||||
mCurrentData.timestamp = currentTimeMs;
|
||||
}
|
||||
|
||||
void AudioReactive::update(fl::u32 currentTimeMs) {
|
||||
// This method handles updates without new sample data
|
||||
// Just apply smoothing and update timestamp
|
||||
smoothResults();
|
||||
mCurrentData.timestamp = currentTimeMs;
|
||||
}
|
||||
|
||||
void AudioReactive::processFFT(const AudioSample& sample) {
|
||||
// Get PCM data from AudioSample
|
||||
const auto& pcmData = sample.pcm();
|
||||
if (pcmData.empty()) return;
|
||||
|
||||
// Use AudioSample's built-in FFT capability
|
||||
sample.fft(&mFFTBins);
|
||||
|
||||
// Map FFT bins to frequency channels using WLED-compatible mapping
|
||||
mapFFTBinsToFrequencyChannels();
|
||||
}
|
||||
|
||||
void AudioReactive::mapFFTBinsToFrequencyChannels() {
|
||||
// Copy FFT results to frequency bins array
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
|
||||
mCurrentData.frequencyBins[i] = mFFTBins.bins_raw[i];
|
||||
} else {
|
||||
mCurrentData.frequencyBins[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pink noise compensation (from WLED)
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= PINK_NOISE_COMPENSATION[i];
|
||||
}
|
||||
|
||||
// Find dominant frequency
|
||||
float maxMagnitude = 0.0f;
|
||||
int maxBin = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (mCurrentData.frequencyBins[i] > maxMagnitude) {
|
||||
maxMagnitude = mCurrentData.frequencyBins[i];
|
||||
maxBin = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert bin index to approximate frequency
|
||||
// Rough approximation based on WLED frequency mapping
|
||||
const float binCenterFrequencies[16] = {
|
||||
64.5f, // Bin 0: 43-86 Hz
|
||||
107.5f, // Bin 1: 86-129 Hz
|
||||
172.5f, // Bin 2: 129-216 Hz
|
||||
258.5f, // Bin 3: 216-301 Hz
|
||||
365.5f, // Bin 4: 301-430 Hz
|
||||
495.0f, // Bin 5: 430-560 Hz
|
||||
689.0f, // Bin 6: 560-818 Hz
|
||||
969.0f, // Bin 7: 818-1120 Hz
|
||||
1270.5f, // Bin 8: 1120-1421 Hz
|
||||
1658.0f, // Bin 9: 1421-1895 Hz
|
||||
2153.5f, // Bin 10: 1895-2412 Hz
|
||||
2713.5f, // Bin 11: 2412-3015 Hz
|
||||
3359.5f, // Bin 12: 3015-3704 Hz
|
||||
4091.5f, // Bin 13: 3704-4479 Hz
|
||||
5792.5f, // Bin 14: 4479-7106 Hz
|
||||
8182.5f // Bin 15: 7106-9259 Hz
|
||||
};
|
||||
|
||||
mCurrentData.dominantFrequency = binCenterFrequencies[maxBin];
|
||||
mCurrentData.magnitude = maxMagnitude;
|
||||
}
|
||||
|
||||
void AudioReactive::updateVolumeAndPeak(const AudioSample& sample) {
|
||||
// Get PCM data from AudioSample
|
||||
const auto& pcmData = sample.pcm();
|
||||
if (pcmData.empty()) {
|
||||
mCurrentData.volume = 0.0f;
|
||||
mCurrentData.volumeRaw = 0.0f;
|
||||
mCurrentData.peak = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Use AudioSample's built-in RMS calculation
|
||||
float rms = sample.rms();
|
||||
|
||||
// Calculate peak from PCM data
|
||||
float maxSample = 0.0f;
|
||||
for (fl::i16 pcmSample : pcmData) {
|
||||
float absSample = (pcmSample < 0) ? -pcmSample : pcmSample;
|
||||
maxSample = (maxSample > absSample) ? maxSample : absSample;
|
||||
}
|
||||
|
||||
// Scale to 0-255 range (approximately)
|
||||
mCurrentData.volumeRaw = rms / 128.0f; // Rough scaling
|
||||
mCurrentData.volume = mCurrentData.volumeRaw;
|
||||
|
||||
// Peak detection
|
||||
mCurrentData.peak = maxSample / 32768.0f * 255.0f;
|
||||
|
||||
// Update AGC tracking
|
||||
if (mConfig.agcEnabled) {
|
||||
// AGC with attack/decay behavior
|
||||
float agcAttackRate = mConfig.attack / 255.0f * 0.2f + 0.01f; // 0.01 to 0.21
|
||||
float agcDecayRate = mConfig.decay / 255.0f * 0.05f + 0.001f; // 0.001 to 0.051
|
||||
|
||||
// Track maximum level with attack/decay
|
||||
if (maxSample > mMaxSample) {
|
||||
// Rising - use attack rate (faster response)
|
||||
mMaxSample = mMaxSample * (1.0f - agcAttackRate) + maxSample * agcAttackRate;
|
||||
} else {
|
||||
// Falling - use decay rate (slower response)
|
||||
mMaxSample = mMaxSample * (1.0f - agcDecayRate) + maxSample * agcDecayRate;
|
||||
}
|
||||
|
||||
// Update AGC multiplier with proper bounds
|
||||
if (mMaxSample > 1000.0f) {
|
||||
float targetLevel = 16384.0f; // Half of full scale
|
||||
float newMultiplier = targetLevel / mMaxSample;
|
||||
|
||||
// Smooth AGC multiplier changes using attack/decay
|
||||
if (newMultiplier > mAGCMultiplier) {
|
||||
// Increasing gain - use attack rate
|
||||
mAGCMultiplier = mAGCMultiplier * (1.0f - agcAttackRate) + newMultiplier * agcAttackRate;
|
||||
} else {
|
||||
// Decreasing gain - use decay rate
|
||||
mAGCMultiplier = mAGCMultiplier * (1.0f - agcDecayRate) + newMultiplier * agcDecayRate;
|
||||
}
|
||||
|
||||
// Clamp multiplier to reasonable bounds
|
||||
mAGCMultiplier = (mAGCMultiplier < 0.1f) ? 0.1f : ((mAGCMultiplier > 10.0f) ? 10.0f : mAGCMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::detectBeat(fl::u32 currentTimeMs) {
|
||||
// Need minimum time since last beat
|
||||
if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
|
||||
mCurrentData.beatDetected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple beat detection based on volume increase
|
||||
float currentVolume = mCurrentData.volume;
|
||||
|
||||
// Beat detected if volume significantly increased
|
||||
if (currentVolume > mPreviousVolume + mVolumeThreshold &&
|
||||
currentVolume > 5.0f) { // Minimum volume threshold
|
||||
mCurrentData.beatDetected = true;
|
||||
mLastBeatTime = currentTimeMs;
|
||||
} else {
|
||||
mCurrentData.beatDetected = false;
|
||||
}
|
||||
|
||||
// Update previous volume for next comparison using attack/decay
|
||||
float beatAttackRate = mConfig.attack / 255.0f * 0.5f + 0.1f; // 0.1 to 0.6
|
||||
float beatDecayRate = mConfig.decay / 255.0f * 0.3f + 0.05f; // 0.05 to 0.35
|
||||
|
||||
if (currentVolume > mPreviousVolume) {
|
||||
// Rising volume - use attack rate (faster tracking)
|
||||
mPreviousVolume = mPreviousVolume * (1.0f - beatAttackRate) + currentVolume * beatAttackRate;
|
||||
} else {
|
||||
// Falling volume - use decay rate (slower tracking)
|
||||
mPreviousVolume = mPreviousVolume * (1.0f - beatDecayRate) + currentVolume * beatDecayRate;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyGain() {
|
||||
// Apply gain setting (0-255 maps to 0.0-2.0 multiplier)
|
||||
float gainMultiplier = static_cast<float>(mConfig.gain) / 128.0f;
|
||||
|
||||
mCurrentData.volume *= gainMultiplier;
|
||||
mCurrentData.volumeRaw *= gainMultiplier;
|
||||
mCurrentData.peak *= gainMultiplier;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= gainMultiplier;
|
||||
}
|
||||
|
||||
// Apply AGC if enabled
|
||||
if (mConfig.agcEnabled) {
|
||||
mCurrentData.volume *= mAGCMultiplier;
|
||||
mCurrentData.volumeRaw *= mAGCMultiplier;
|
||||
mCurrentData.peak *= mAGCMultiplier;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= mAGCMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyScaling() {
|
||||
// Apply scaling mode to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
float value = mCurrentData.frequencyBins[i];
|
||||
|
||||
switch (mConfig.scalingMode) {
|
||||
case 1: // Logarithmic scaling
|
||||
if (value > 1.0f) {
|
||||
value = logf(value) * 20.0f; // Scale factor
|
||||
} else {
|
||||
value = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Linear scaling (no change)
|
||||
// value remains as-is
|
||||
break;
|
||||
|
||||
case 3: // Square root scaling
|
||||
if (value > 0.0f) {
|
||||
value = sqrtf(value) * 8.0f; // Scale factor
|
||||
} else {
|
||||
value = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0: // No scaling
|
||||
default:
|
||||
// value remains as-is
|
||||
break;
|
||||
}
|
||||
|
||||
mCurrentData.frequencyBins[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::smoothResults() {
|
||||
// Attack/decay smoothing - different rates for rising vs falling values
|
||||
// Convert attack/decay times to smoothing factors
|
||||
// Shorter times = less smoothing (faster response)
|
||||
float attackFactor = 1.0f - (mConfig.attack / 255.0f * 0.9f); // Range: 0.1 to 1.0
|
||||
float decayFactor = 1.0f - (mConfig.decay / 255.0f * 0.95f); // Range: 0.05 to 1.0
|
||||
|
||||
// Apply attack/decay smoothing to volume
|
||||
if (mCurrentData.volume > mSmoothedData.volume) {
|
||||
// Rising - use attack time (faster response)
|
||||
mSmoothedData.volume = mSmoothedData.volume * (1.0f - attackFactor) +
|
||||
mCurrentData.volume * attackFactor;
|
||||
} else {
|
||||
// Falling - use decay time (slower response)
|
||||
mSmoothedData.volume = mSmoothedData.volume * (1.0f - decayFactor) +
|
||||
mCurrentData.volume * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to volumeRaw
|
||||
if (mCurrentData.volumeRaw > mSmoothedData.volumeRaw) {
|
||||
mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - attackFactor) +
|
||||
mCurrentData.volumeRaw * attackFactor;
|
||||
} else {
|
||||
mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - decayFactor) +
|
||||
mCurrentData.volumeRaw * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to peak
|
||||
if (mCurrentData.peak > mSmoothedData.peak) {
|
||||
mSmoothedData.peak = mSmoothedData.peak * (1.0f - attackFactor) +
|
||||
mCurrentData.peak * attackFactor;
|
||||
} else {
|
||||
mSmoothedData.peak = mSmoothedData.peak * (1.0f - decayFactor) +
|
||||
mCurrentData.peak * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (mCurrentData.frequencyBins[i] > mSmoothedData.frequencyBins[i]) {
|
||||
// Rising - use attack time
|
||||
mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - attackFactor) +
|
||||
mCurrentData.frequencyBins[i] * attackFactor;
|
||||
} else {
|
||||
// Falling - use decay time
|
||||
mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - decayFactor) +
|
||||
mCurrentData.frequencyBins[i] * decayFactor;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy non-smoothed values
|
||||
mSmoothedData.beatDetected = mCurrentData.beatDetected;
|
||||
mSmoothedData.dominantFrequency = mCurrentData.dominantFrequency;
|
||||
mSmoothedData.magnitude = mCurrentData.magnitude;
|
||||
mSmoothedData.timestamp = mCurrentData.timestamp;
|
||||
}
|
||||
|
||||
const AudioData& AudioReactive::getData() const {
|
||||
return mCurrentData;
|
||||
}
|
||||
|
||||
const AudioData& AudioReactive::getSmoothedData() const {
|
||||
return mSmoothedData;
|
||||
}
|
||||
|
||||
float AudioReactive::getVolume() const {
|
||||
return mCurrentData.volume;
|
||||
}
|
||||
|
||||
float AudioReactive::getBass() const {
|
||||
// Average of bins 0-1 (sub-bass and bass)
|
||||
return (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
|
||||
}
|
||||
|
||||
float AudioReactive::getMid() const {
|
||||
// Average of bins 6-7 (midrange around 1kHz)
|
||||
return (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
|
||||
}
|
||||
|
||||
float AudioReactive::getTreble() const {
|
||||
// Average of bins 14-15 (high frequencies)
|
||||
return (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
|
||||
}
|
||||
|
||||
bool AudioReactive::isBeat() const {
|
||||
return mCurrentData.beatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isBassBeat() const {
|
||||
return mCurrentData.bassBeatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isMidBeat() const {
|
||||
return mCurrentData.midBeatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isTrebleBeat() const {
|
||||
return mCurrentData.trebleBeatDetected;
|
||||
}
|
||||
|
||||
float AudioReactive::getSpectralFlux() const {
|
||||
return mCurrentData.spectralFlux;
|
||||
}
|
||||
|
||||
float AudioReactive::getBassEnergy() const {
|
||||
return mCurrentData.bassEnergy;
|
||||
}
|
||||
|
||||
float AudioReactive::getMidEnergy() const {
|
||||
return mCurrentData.midEnergy;
|
||||
}
|
||||
|
||||
float AudioReactive::getTrebleEnergy() const {
|
||||
return mCurrentData.trebleEnergy;
|
||||
}
|
||||
|
||||
fl::u8 AudioReactive::volumeToScale255() const {
|
||||
float vol = (mCurrentData.volume < 0.0f) ? 0.0f : ((mCurrentData.volume > 255.0f) ? 255.0f : mCurrentData.volume);
|
||||
return static_cast<fl::u8>(vol);
|
||||
}
|
||||
|
||||
CRGB AudioReactive::volumeToColor(const CRGBPalette16& /* palette */) const {
|
||||
fl::u8 index = volumeToScale255();
|
||||
// Simplified color palette lookup
|
||||
return CRGB(index, index, index); // For now, return grayscale
|
||||
}
|
||||
|
||||
fl::u8 AudioReactive::frequencyToScale255(fl::u8 binIndex) const {
|
||||
if (binIndex >= 16) return 0;
|
||||
|
||||
float value = (mCurrentData.frequencyBins[binIndex] < 0.0f) ? 0.0f :
|
||||
((mCurrentData.frequencyBins[binIndex] > 255.0f) ? 255.0f : mCurrentData.frequencyBins[binIndex]);
|
||||
return static_cast<fl::u8>(value);
|
||||
}
|
||||
|
||||
// Enhanced beat detection methods
|
||||
void AudioReactive::calculateBandEnergies() {
|
||||
// Calculate energy for bass frequencies (bins 0-1)
|
||||
mCurrentData.bassEnergy = (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
|
||||
|
||||
// Calculate energy for mid frequencies (bins 6-7)
|
||||
mCurrentData.midEnergy = (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
|
||||
|
||||
// Calculate energy for treble frequencies (bins 14-15)
|
||||
mCurrentData.trebleEnergy = (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
|
||||
}
|
||||
|
||||
void AudioReactive::updateSpectralFlux() {
|
||||
if (!mSpectralFluxDetector) {
|
||||
mCurrentData.spectralFlux = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate spectral flux from current and previous frequency bins
|
||||
mCurrentData.spectralFlux = mSpectralFluxDetector->calculateSpectralFlux(
|
||||
mCurrentData.frequencyBins,
|
||||
mPreviousMagnitudes.data()
|
||||
);
|
||||
|
||||
// Update previous magnitudes for next frame
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mPreviousMagnitudes[i] = mCurrentData.frequencyBins[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::detectEnhancedBeats(fl::u32 currentTimeMs) {
|
||||
// Reset beat flags
|
||||
mCurrentData.bassBeatDetected = false;
|
||||
mCurrentData.midBeatDetected = false;
|
||||
mCurrentData.trebleBeatDetected = false;
|
||||
|
||||
// Skip if enhanced beat detection is disabled
|
||||
if (!mConfig.enableSpectralFlux && !mConfig.enableMultiBand) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need minimum time since last beat for enhanced detection too
|
||||
if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spectral flux-based beat detection
|
||||
if (mConfig.enableSpectralFlux && mSpectralFluxDetector) {
|
||||
bool onsetDetected = mSpectralFluxDetector->detectOnset(
|
||||
mCurrentData.frequencyBins,
|
||||
mPreviousMagnitudes.data()
|
||||
);
|
||||
|
||||
if (onsetDetected) {
|
||||
// Enhance the traditional beat detection when spectral flux confirms
|
||||
mCurrentData.beatDetected = true;
|
||||
mLastBeatTime = currentTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-band beat detection
|
||||
if (mConfig.enableMultiBand) {
|
||||
// Bass beat detection (bins 0-1)
|
||||
if (mCurrentData.bassEnergy > mConfig.bassThreshold) {
|
||||
mCurrentData.bassBeatDetected = true;
|
||||
}
|
||||
|
||||
// Mid beat detection (bins 6-7)
|
||||
if (mCurrentData.midEnergy > mConfig.midThreshold) {
|
||||
mCurrentData.midBeatDetected = true;
|
||||
}
|
||||
|
||||
// Treble beat detection (bins 14-15)
|
||||
if (mCurrentData.trebleEnergy > mConfig.trebleThreshold) {
|
||||
mCurrentData.trebleBeatDetected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyPerceptualWeighting() {
|
||||
// Apply perceptual weighting if available
|
||||
if (mPerceptualWeighting) {
|
||||
mPerceptualWeighting->applyAWeighting(mCurrentData);
|
||||
|
||||
// Apply loudness compensation with reference level of 50.0f
|
||||
mPerceptualWeighting->applyLoudnessCompensation(mCurrentData, 50.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
float AudioReactive::mapFrequencyBin(int fromBin, int toBin) {
|
||||
if (fromBin < 0 || toBin >= static_cast<int>(mFFTBins.size()) || fromBin > toBin) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float sum = 0.0f;
|
||||
for (int i = fromBin; i <= toBin; ++i) {
|
||||
if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
|
||||
sum += mFFTBins.bins_raw[i];
|
||||
}
|
||||
}
|
||||
|
||||
return sum / static_cast<float>(toBin - fromBin + 1);
|
||||
}
|
||||
|
||||
float AudioReactive::computeRMS(const fl::vector<fl::i16>& samples) {
|
||||
if (samples.empty()) return 0.0f;
|
||||
|
||||
float sumSquares = 0.0f;
|
||||
for (const auto& sample : samples) {
|
||||
float f = static_cast<float>(sample);
|
||||
sumSquares += f * f;
|
||||
}
|
||||
|
||||
return sqrtf(sumSquares / samples.size());
|
||||
}
|
||||
|
||||
// SpectralFluxDetector implementation
|
||||
SpectralFluxDetector::SpectralFluxDetector()
|
||||
: mFluxThreshold(0.1f)
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
, mHistoryIndex(0)
|
||||
#endif
|
||||
{
|
||||
// Initialize previous magnitudes to zero
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Initialize flux history to zero
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
mFluxHistory[i] = 0.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SpectralFluxDetector::~SpectralFluxDetector() = default;
|
||||
|
||||
void SpectralFluxDetector::reset() {
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
mFluxHistory[i] = 0.0f;
|
||||
}
|
||||
mHistoryIndex = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SpectralFluxDetector::detectOnset(const float* currentBins, const float* /* previousBins */) {
|
||||
float flux = calculateSpectralFlux(currentBins, mPreviousMagnitudes.data());
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Store flux in history for adaptive threshold calculation
|
||||
mFluxHistory[mHistoryIndex] = flux;
|
||||
mHistoryIndex = (mHistoryIndex + 1) % mFluxHistory.size();
|
||||
|
||||
float adaptiveThreshold = calculateAdaptiveThreshold();
|
||||
return flux > adaptiveThreshold;
|
||||
#else
|
||||
// Simple fixed threshold for memory-constrained platforms
|
||||
return flux > mFluxThreshold;
|
||||
#endif
|
||||
}
|
||||
|
||||
float SpectralFluxDetector::calculateSpectralFlux(const float* currentBins, const float* previousBins) {
|
||||
float flux = 0.0f;
|
||||
|
||||
// Calculate spectral flux as sum of positive differences
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
float diff = currentBins[i] - previousBins[i];
|
||||
if (diff > 0.0f) {
|
||||
flux += diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous magnitudes for next calculation
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mPreviousMagnitudes[i] = currentBins[i];
|
||||
}
|
||||
|
||||
return flux;
|
||||
}
|
||||
|
||||
void SpectralFluxDetector::setThreshold(float threshold) {
|
||||
mFluxThreshold = threshold;
|
||||
}
|
||||
|
||||
float SpectralFluxDetector::getThreshold() const {
|
||||
return mFluxThreshold;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
float SpectralFluxDetector::calculateAdaptiveThreshold() {
|
||||
// Calculate moving average of flux history
|
||||
float sum = 0.0f;
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
sum += mFluxHistory[i];
|
||||
}
|
||||
float average = sum / mFluxHistory.size();
|
||||
|
||||
// Adaptive threshold is base threshold plus some multiple of recent average
|
||||
return mFluxThreshold + (average * 0.5f);
|
||||
}
|
||||
#endif
|
||||
|
||||
// BeatDetectors implementation
|
||||
BeatDetectors::BeatDetectors()
|
||||
: mBassEnergy(0.0f), mMidEnergy(0.0f), mTrebleEnergy(0.0f)
|
||||
, mPreviousBassEnergy(0.0f), mPreviousMidEnergy(0.0f), mPreviousTrebleEnergy(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
BeatDetectors::~BeatDetectors() = default;
|
||||
|
||||
void BeatDetectors::reset() {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
bass.reset();
|
||||
mid.reset();
|
||||
treble.reset();
|
||||
#else
|
||||
combined.reset();
|
||||
#endif
|
||||
|
||||
mBassEnergy = 0.0f;
|
||||
mMidEnergy = 0.0f;
|
||||
mTrebleEnergy = 0.0f;
|
||||
mPreviousBassEnergy = 0.0f;
|
||||
mPreviousMidEnergy = 0.0f;
|
||||
mPreviousTrebleEnergy = 0.0f;
|
||||
}
|
||||
|
||||
void BeatDetectors::detectBeats(const float* frequencyBins, AudioData& audioData) {
|
||||
// Calculate current band energies
|
||||
mBassEnergy = (frequencyBins[0] + frequencyBins[1]) / 2.0f;
|
||||
mMidEnergy = (frequencyBins[6] + frequencyBins[7]) / 2.0f;
|
||||
mTrebleEnergy = (frequencyBins[14] + frequencyBins[15]) / 2.0f;
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Use separate detectors for each band
|
||||
audioData.bassBeatDetected = bass.detectOnset(&mBassEnergy, &mPreviousBassEnergy);
|
||||
audioData.midBeatDetected = mid.detectOnset(&mMidEnergy, &mPreviousMidEnergy);
|
||||
audioData.trebleBeatDetected = treble.detectOnset(&mTrebleEnergy, &mPreviousTrebleEnergy);
|
||||
#else
|
||||
// Use simple threshold detection for memory-constrained platforms
|
||||
audioData.bassBeatDetected = (mBassEnergy > mPreviousBassEnergy * 1.3f) && (mBassEnergy > 0.1f);
|
||||
audioData.midBeatDetected = (mMidEnergy > mPreviousMidEnergy * 1.25f) && (mMidEnergy > 0.08f);
|
||||
audioData.trebleBeatDetected = (mTrebleEnergy > mPreviousTrebleEnergy * 1.2f) && (mTrebleEnergy > 0.05f);
|
||||
#endif
|
||||
|
||||
// Update previous energies
|
||||
mPreviousBassEnergy = mBassEnergy;
|
||||
mPreviousMidEnergy = mMidEnergy;
|
||||
mPreviousTrebleEnergy = mTrebleEnergy;
|
||||
}
|
||||
|
||||
void BeatDetectors::setThresholds(float bassThresh, float midThresh, float trebleThresh) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
bass.setThreshold(bassThresh);
|
||||
mid.setThreshold(midThresh);
|
||||
treble.setThreshold(trebleThresh);
|
||||
#else
|
||||
combined.setThreshold((bassThresh + midThresh + trebleThresh) / 3.0f);
|
||||
#endif
|
||||
}
|
||||
|
||||
// PerceptualWeighting implementation
|
||||
PerceptualWeighting::PerceptualWeighting()
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
: mHistoryIndex(0)
|
||||
#endif
|
||||
{
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Initialize loudness history to zero
|
||||
for (fl::size i = 0; i < mLoudnessHistory.size(); ++i) {
|
||||
mLoudnessHistory[i] = 0.0f;
|
||||
}
|
||||
// Suppress unused warning until mHistoryIndex is implemented
|
||||
(void)mHistoryIndex;
|
||||
#endif
|
||||
}
|
||||
|
||||
PerceptualWeighting::~PerceptualWeighting() = default;
|
||||
|
||||
void PerceptualWeighting::applyAWeighting(AudioData& data) const {
|
||||
// Apply A-weighting coefficients to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
data.frequencyBins[i] *= A_WEIGHTING_COEFFS[i];
|
||||
}
|
||||
}
|
||||
|
||||
void PerceptualWeighting::applyLoudnessCompensation(AudioData& data, float referenceLevel) const {
|
||||
// Calculate current loudness level
|
||||
float currentLoudness = data.volume;
|
||||
|
||||
// Calculate compensation factor based on difference from reference
|
||||
float compensationFactor = 1.0f;
|
||||
if (currentLoudness < referenceLevel) {
|
||||
// Boost quiet signals
|
||||
compensationFactor = 1.0f + (referenceLevel - currentLoudness) / referenceLevel * 0.3f;
|
||||
} else if (currentLoudness > referenceLevel * 1.5f) {
|
||||
// Slightly reduce very loud signals
|
||||
compensationFactor = 1.0f - (currentLoudness - referenceLevel * 1.5f) / (referenceLevel * 2.0f) * 0.2f;
|
||||
}
|
||||
|
||||
// Apply compensation to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
data.frequencyBins[i] *= compensationFactor;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Store in history for future adaptive compensation (not implemented yet)
|
||||
// This would be used for more sophisticated dynamic range compensation
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
231
libraries/FastLED/src/fl/audio_reactive.h
Normal file
231
libraries/FastLED/src/fl/audio_reactive.h
Normal file
@@ -0,0 +1,231 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/array.h"
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/colorutils.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations for enhanced beat detection
|
||||
class SpectralFluxDetector;
|
||||
class PerceptualWeighting;
|
||||
|
||||
// Audio data structure - matches original WLED output with extensions
|
||||
struct AudioData {
|
||||
float volume = 0.0f; // Overall volume level (0-255)
|
||||
float volumeRaw = 0.0f; // Raw volume without smoothing
|
||||
float peak = 0.0f; // Peak level (0-255)
|
||||
bool beatDetected = false; // Beat detection flag
|
||||
float frequencyBins[16] = {0}; // 16 frequency bins (matches WLED NUM_GEQ_CHANNELS)
|
||||
float dominantFrequency = 0.0f; // Major peak frequency (Hz)
|
||||
float magnitude = 0.0f; // FFT magnitude of dominant frequency
|
||||
fl::u32 timestamp = 0; // millis() when data was captured
|
||||
|
||||
// Enhanced beat detection fields
|
||||
bool bassBeatDetected = false; // Bass-specific beat detection
|
||||
bool midBeatDetected = false; // Mid-range beat detection
|
||||
bool trebleBeatDetected = false; // Treble beat detection
|
||||
float spectralFlux = 0.0f; // Current spectral flux value
|
||||
float bassEnergy = 0.0f; // Energy in bass frequencies (0-1)
|
||||
float midEnergy = 0.0f; // Energy in mid frequencies (6-7)
|
||||
float trebleEnergy = 0.0f; // Energy in treble frequencies (14-15)
|
||||
};
|
||||
|
||||
struct AudioReactiveConfig {
|
||||
fl::u8 gain = 128; // Input gain (0-255)
|
||||
fl::u8 sensitivity = 128; // AGC sensitivity
|
||||
bool agcEnabled = true; // Auto gain control
|
||||
bool noiseGate = true; // Noise gate
|
||||
fl::u8 attack = 50; // Attack time (ms) - how fast to respond to increases
|
||||
fl::u8 decay = 200; // Decay time (ms) - how slow to respond to decreases
|
||||
u16 sampleRate = 22050; // Sample rate (Hz)
|
||||
fl::u8 scalingMode = 3; // 0=none, 1=log, 2=linear, 3=sqrt
|
||||
|
||||
// Enhanced beat detection configuration
|
||||
bool enableSpectralFlux = true; // Enable spectral flux-based beat detection
|
||||
bool enableMultiBand = true; // Enable multi-band beat detection
|
||||
float spectralFluxThreshold = 0.1f; // Threshold for spectral flux detection
|
||||
float bassThreshold = 0.15f; // Threshold for bass beat detection
|
||||
float midThreshold = 0.12f; // Threshold for mid beat detection
|
||||
float trebleThreshold = 0.08f; // Threshold for treble beat detection
|
||||
};
|
||||
|
||||
class AudioReactive {
|
||||
public:
|
||||
AudioReactive();
|
||||
~AudioReactive();
|
||||
|
||||
// Setup
|
||||
void begin(const AudioReactiveConfig& config = AudioReactiveConfig{});
|
||||
void setConfig(const AudioReactiveConfig& config);
|
||||
|
||||
// Process audio sample - this does all the work immediately
|
||||
void processSample(const AudioSample& sample);
|
||||
|
||||
// Optional: update smoothing without new sample data
|
||||
void update(fl::u32 currentTimeMs);
|
||||
|
||||
// Data access
|
||||
const AudioData& getData() const;
|
||||
const AudioData& getSmoothedData() const;
|
||||
|
||||
// Convenience accessors
|
||||
float getVolume() const;
|
||||
float getBass() const; // Average of bins 0-1
|
||||
float getMid() const; // Average of bins 6-7
|
||||
float getTreble() const; // Average of bins 14-15
|
||||
bool isBeat() const;
|
||||
|
||||
// Enhanced beat detection accessors
|
||||
bool isBassBeat() const;
|
||||
bool isMidBeat() const;
|
||||
bool isTrebleBeat() const;
|
||||
float getSpectralFlux() const;
|
||||
float getBassEnergy() const;
|
||||
float getMidEnergy() const;
|
||||
float getTrebleEnergy() const;
|
||||
|
||||
// Effect helpers
|
||||
fl::u8 volumeToScale255() const;
|
||||
CRGB volumeToColor(const CRGBPalette16& palette) const;
|
||||
fl::u8 frequencyToScale255(fl::u8 binIndex) const;
|
||||
|
||||
private:
|
||||
// Internal processing methods
|
||||
void processFFT(const AudioSample& sample);
|
||||
void mapFFTBinsToFrequencyChannels();
|
||||
void updateVolumeAndPeak(const AudioSample& sample);
|
||||
void detectBeat(fl::u32 currentTimeMs);
|
||||
void smoothResults();
|
||||
void applyScaling();
|
||||
void applyGain();
|
||||
|
||||
// Enhanced beat detection methods
|
||||
void detectEnhancedBeats(fl::u32 currentTimeMs);
|
||||
void calculateBandEnergies();
|
||||
void updateSpectralFlux();
|
||||
void applyPerceptualWeighting();
|
||||
|
||||
// Helper methods
|
||||
float mapFrequencyBin(int fromBin, int toBin);
|
||||
float computeRMS(const fl::vector<fl::i16>& samples);
|
||||
|
||||
// Configuration
|
||||
AudioReactiveConfig mConfig;
|
||||
|
||||
// FFT processing
|
||||
FFT mFFT;
|
||||
FFTBins mFFTBins;
|
||||
|
||||
// Audio data
|
||||
AudioData mCurrentData;
|
||||
AudioData mSmoothedData;
|
||||
|
||||
// Processing state
|
||||
fl::u32 mLastBeatTime = 0;
|
||||
static constexpr fl::u32 BEAT_COOLDOWN = 100; // 100ms minimum between beats
|
||||
|
||||
// Volume tracking for beat detection
|
||||
float mPreviousVolume = 0.0f;
|
||||
float mVolumeThreshold = 10.0f;
|
||||
|
||||
// Pink noise compensation (from WLED)
|
||||
static constexpr float PINK_NOISE_COMPENSATION[16] = {
|
||||
1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f,
|
||||
1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f
|
||||
};
|
||||
|
||||
// AGC state
|
||||
float mAGCMultiplier = 1.0f;
|
||||
float mMaxSample = 0.0f;
|
||||
float mAverageLevel = 0.0f;
|
||||
|
||||
// Enhanced beat detection components
|
||||
fl::unique_ptr<SpectralFluxDetector> mSpectralFluxDetector;
|
||||
fl::unique_ptr<PerceptualWeighting> mPerceptualWeighting;
|
||||
|
||||
// Enhanced beat detection state
|
||||
fl::array<float, 16> mPreviousMagnitudes;
|
||||
};
|
||||
|
||||
// Spectral flux-based onset detection for enhanced beat detection
|
||||
class SpectralFluxDetector {
|
||||
public:
|
||||
SpectralFluxDetector();
|
||||
~SpectralFluxDetector();
|
||||
|
||||
void reset();
|
||||
bool detectOnset(const float* currentBins, const float* previousBins);
|
||||
float calculateSpectralFlux(const float* currentBins, const float* previousBins);
|
||||
void setThreshold(float threshold);
|
||||
float getThreshold() const;
|
||||
|
||||
private:
|
||||
float mFluxThreshold;
|
||||
fl::array<float, 16> mPreviousMagnitudes;
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
fl::array<float, 32> mFluxHistory; // For advanced smoothing
|
||||
fl::size mHistoryIndex;
|
||||
float calculateAdaptiveThreshold();
|
||||
#endif
|
||||
};
|
||||
|
||||
// Multi-band beat detection for different frequency ranges
|
||||
struct BeatDetectors {
|
||||
BeatDetectors();
|
||||
~BeatDetectors();
|
||||
|
||||
void reset();
|
||||
void detectBeats(const float* frequencyBins, AudioData& audioData);
|
||||
void setThresholds(float bassThresh, float midThresh, float trebleThresh);
|
||||
|
||||
private:
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
SpectralFluxDetector bass; // 20-200 Hz (bins 0-1)
|
||||
SpectralFluxDetector mid; // 200-2000 Hz (bins 6-7)
|
||||
SpectralFluxDetector treble; // 2000-20000 Hz (bins 14-15)
|
||||
#else
|
||||
SpectralFluxDetector combined; // Single detector for memory-constrained
|
||||
#endif
|
||||
|
||||
// Energy tracking for band-specific thresholds
|
||||
float mBassEnergy;
|
||||
float mMidEnergy;
|
||||
float mTrebleEnergy;
|
||||
float mPreviousBassEnergy;
|
||||
float mPreviousMidEnergy;
|
||||
float mPreviousTrebleEnergy;
|
||||
};
|
||||
|
||||
// Perceptual audio weighting for psychoacoustic processing
|
||||
class PerceptualWeighting {
|
||||
public:
|
||||
PerceptualWeighting();
|
||||
~PerceptualWeighting();
|
||||
|
||||
void applyAWeighting(AudioData& data) const;
|
||||
void applyLoudnessCompensation(AudioData& data, float referenceLevel) const;
|
||||
|
||||
private:
|
||||
// A-weighting coefficients for 16-bin frequency analysis
|
||||
static constexpr float A_WEIGHTING_COEFFS[16] = {
|
||||
0.5f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.4f, 1.4f,
|
||||
1.3f, 1.2f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f
|
||||
};
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
fl::array<float, 16> mLoudnessHistory; // For dynamic compensation
|
||||
fl::size mHistoryIndex;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
8
libraries/FastLED/src/fl/avr_disallowed.h
Normal file
8
libraries/FastLED/src/fl/avr_disallowed.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __AVR__
|
||||
#define AVR_DISALLOWED \
|
||||
[[deprecated("This function or class is deprecated on AVR.")]]
|
||||
#else
|
||||
#define AVR_DISALLOWED
|
||||
#endif
|
||||
74
libraries/FastLED/src/fl/bit_cast.h
Normal file
74
libraries/FastLED/src/fl/bit_cast.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
// What is bit cast?
|
||||
// Bit cast is a safe version of reinterpret_cast that is robust against strict aliasing rules
|
||||
// that are used in aggressive compiler optimizations.
|
||||
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// bit_cast - Safe type-punning utility (C++20 std::bit_cast equivalent)
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// Helper trait for bitcast - check if a type can be bitcast (relax POD requirement)
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible {
|
||||
static constexpr bool value = fl::is_integral<T>::value ||
|
||||
fl::is_floating_point<T>::value ||
|
||||
fl::is_pod<T>::value;
|
||||
};
|
||||
|
||||
// Specializations for const types
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible<const T> {
|
||||
static constexpr bool value = is_bitcast_compatible<T>::value;
|
||||
};
|
||||
|
||||
// Specializations for pointer types
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible<T*> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
// C++20-style bit_cast for safe type reinterpretation
|
||||
// Uses union for zero-cost type punning - compiler optimizes to direct assignment
|
||||
template <typename To, typename From>
|
||||
To bit_cast(const From& from) noexcept {
|
||||
static_assert(sizeof(To) == sizeof(From), "bit_cast: types must have the same size");
|
||||
static_assert(is_bitcast_compatible<To>::value, "bit_cast: destination type must be bitcast compatible");
|
||||
static_assert(is_bitcast_compatible<From>::value, "bit_cast: source type must be bitcast compatible");
|
||||
|
||||
union { // robust against strict aliasing rules
|
||||
From from_val;
|
||||
To to_val;
|
||||
} u;
|
||||
u.from_val = from;
|
||||
return u.to_val;
|
||||
}
|
||||
|
||||
// Overload for pointer types - converts storage pointer to typed pointer safely
|
||||
template <typename To>
|
||||
To* bit_cast_ptr(void* storage) noexcept {
|
||||
return bit_cast<To*>(storage);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
const To* bit_cast_ptr(const void* storage) noexcept {
|
||||
return bit_cast<const To*>(storage);
|
||||
}
|
||||
|
||||
// Additional utility for uptr conversions (common pattern in the codebase)
|
||||
template <typename T>
|
||||
uptr ptr_to_int(T* ptr) noexcept {
|
||||
return bit_cast<uptr>(ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* int_to_ptr(uptr value) noexcept {
|
||||
return bit_cast<T*>(value);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
28
libraries/FastLED/src/fl/bitset.cpp
Normal file
28
libraries/FastLED/src/fl/bitset.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "fl/bitset.h"
|
||||
|
||||
#include "fl/string.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace detail {
|
||||
void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst) {
|
||||
fl::string& result = *dst;
|
||||
constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16); // 16 bits per block
|
||||
|
||||
for (fl::u32 i = 0; i < bit_count; ++i) {
|
||||
const fl::u32 block_idx = i / bits_per_block;
|
||||
const fl::u32 bit_offset = i % bits_per_block;
|
||||
|
||||
// Extract the bit from the block
|
||||
bool bit_value = (bit_data[block_idx] >> bit_offset) & 1;
|
||||
result.append(bit_value ? "1" : "0");
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Implementation for bitset_dynamic::to_string
|
||||
void bitset_dynamic::to_string(string* dst) const {
|
||||
detail::to_string(_blocks, _size, dst);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
663
libraries/FastLED/src/fl/bitset.h
Normal file
663
libraries/FastLED/src/fl/bitset.h
Normal file
@@ -0,0 +1,663 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/bitset_dynamic.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(double-promotion)
|
||||
FL_DISABLE_WARNING(float-conversion)
|
||||
FL_DISABLE_WARNING(sign-conversion)
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <fl::u32 N> class BitsetInlined;
|
||||
|
||||
template <fl::u32 N> class BitsetFixed;
|
||||
|
||||
class string;
|
||||
template <fl::u32 N = 16>
|
||||
using bitset = BitsetInlined<N>; // inlined but can go bigger.
|
||||
|
||||
template <fl::u32 N>
|
||||
using bitset_fixed = BitsetFixed<N>; // fixed size, no dynamic allocation.
|
||||
|
||||
|
||||
// TODO: move this to fl/math.h
|
||||
template<typename IntType>
|
||||
inline fl::u8 popcount(IntType value) {
|
||||
return static_cast<fl::u8>(__builtin_popcount(value));
|
||||
}
|
||||
|
||||
template<typename IntType>
|
||||
inline fl::u8 countr_zero(IntType value) {
|
||||
return static_cast<fl::u8>(__builtin_ctz(value));
|
||||
}
|
||||
|
||||
/// A simple fixed-size Bitset implementation similar to std::Bitset.
|
||||
template <fl::u32 N> class BitsetFixed {
|
||||
private:
|
||||
static_assert(sizeof(fl::u16) == 2, "u16 should be 2 bytes");
|
||||
static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16);
|
||||
static constexpr fl::u32 block_count =
|
||||
(N + bits_per_block - 1) / bits_per_block;
|
||||
using block_type = fl::u16;
|
||||
|
||||
// Underlying blocks storing bits
|
||||
block_type _blocks[block_count];
|
||||
|
||||
public:
|
||||
struct Proxy {
|
||||
BitsetFixed &_bitset;
|
||||
fl::u32 _pos;
|
||||
|
||||
Proxy(BitsetFixed &bitset, fl::u32 pos) : _bitset(bitset), _pos(pos) {}
|
||||
|
||||
Proxy &operator=(bool value) {
|
||||
_bitset.set(_pos, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return _bitset.test(_pos); }
|
||||
};
|
||||
|
||||
Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); }
|
||||
|
||||
/// Constructs a BitsetFixed with all bits reset.
|
||||
constexpr BitsetFixed() noexcept : _blocks{} {}
|
||||
|
||||
void to_string(string* dst) const {
|
||||
detail::to_string(_blocks, N, dst);
|
||||
}
|
||||
|
||||
/// Resets all bits to zero.
|
||||
void reset() noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears the bit at position pos.
|
||||
BitsetFixed &set(fl::u32 pos, bool value = true) {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
if (value) {
|
||||
_blocks[idx] |= (block_type(1) << off);
|
||||
} else {
|
||||
_blocks[idx] &= ~(block_type(1) << off);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void assign(fl::size n, bool value) {
|
||||
if (n > N) {
|
||||
n = N;
|
||||
}
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
set(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the bit at position pos.
|
||||
BitsetFixed &reset(fl::u32 pos) { return set(pos, false); }
|
||||
|
||||
/// Flips (toggles) the bit at position pos.
|
||||
BitsetFixed &flip(fl::u32 pos) {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] ^= (block_type(1) << off);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Flips all bits.
|
||||
BitsetFixed &flip() noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] = ~_blocks[i];
|
||||
}
|
||||
// Mask out unused high bits in the last block
|
||||
if (N % bits_per_block != 0) {
|
||||
const fl::u32 extra = bits_per_block - (N % bits_per_block);
|
||||
_blocks[block_count - 1] &= (~block_type(0) >> extra);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Tests whether the bit at position pos is set.
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
return (_blocks[idx] >> off) & 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns the value of the bit at position pos.
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Returns the number of set bits.
|
||||
fl::u32 count() const noexcept {
|
||||
fl::u32 cnt = 0;
|
||||
// Count bits in all complete blocks
|
||||
for (fl::u32 i = 0; i < block_count - 1; ++i) {
|
||||
cnt += fl::popcount(_blocks[i]);
|
||||
}
|
||||
|
||||
// For the last block, we need to be careful about counting only valid
|
||||
// bits
|
||||
if (block_count > 0) {
|
||||
block_type last_block = _blocks[block_count - 1];
|
||||
// If N is not a multiple of bits_per_block, mask out the unused
|
||||
// bits
|
||||
if (N % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = N % bits_per_block;
|
||||
// Create a mask with only the valid bits set to 1
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: ((block_type(1) << valid_bits) - 1);
|
||||
last_block &= mask;
|
||||
}
|
||||
cnt += fl::popcount(last_block);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/// Queries.
|
||||
bool any() const noexcept { return count() > 0; }
|
||||
bool none() const noexcept { return count() == 0; }
|
||||
bool all() const noexcept {
|
||||
if (N == 0)
|
||||
return true;
|
||||
|
||||
// Check all complete blocks
|
||||
for (fl::u32 i = 0; i < block_count - 1; ++i) {
|
||||
if (_blocks[i] != ~block_type(0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the last block
|
||||
if (block_count > 0) {
|
||||
block_type mask;
|
||||
if (N % bits_per_block != 0) {
|
||||
// Create a mask for the valid bits in the last block
|
||||
mask = (block_type(1) << (N % bits_per_block)) - 1;
|
||||
} else {
|
||||
mask = static_cast<block_type>(~block_type(0));
|
||||
}
|
||||
|
||||
if ((_blocks[block_count - 1] & mask) != mask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Bitwise AND
|
||||
BitsetFixed &operator&=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] &= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Bitwise OR
|
||||
BitsetFixed &operator|=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] |= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Bitwise XOR
|
||||
BitsetFixed &operator^=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] ^= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Size of the BitsetFixed (number of bits).
|
||||
constexpr fl::u32 size() const noexcept { return N; }
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
// If offset is beyond our size, no match possible
|
||||
if (offset >= N) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate which block to start from
|
||||
fl::u32 start_block = offset / bits_per_block;
|
||||
fl::u32 start_bit = offset % bits_per_block;
|
||||
|
||||
for (fl::u32 block_idx = start_block; block_idx < block_count; ++block_idx) {
|
||||
block_type current_block = _blocks[block_idx];
|
||||
|
||||
// For the last block, we need to mask out unused bits
|
||||
if (block_idx == block_count - 1 && N % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = N % bits_per_block;
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: ((block_type(1) << valid_bits) - 1);
|
||||
current_block &= mask;
|
||||
}
|
||||
|
||||
// If looking for false bits, invert the block
|
||||
if (!test_value) {
|
||||
current_block = ~current_block;
|
||||
}
|
||||
|
||||
// For the first block, mask out bits before the offset
|
||||
if (block_idx == start_block && start_bit > 0) {
|
||||
current_block &= ~((block_type(1) << start_bit) - 1);
|
||||
}
|
||||
|
||||
// If there are any matching bits in this block
|
||||
if (current_block != 0) {
|
||||
// Find the first set bit
|
||||
fl::u32 bit_pos = fl::countr_zero(current_block);
|
||||
fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos;
|
||||
|
||||
// Make sure we haven't gone past the end of the bitset
|
||||
if (absolute_pos < N) {
|
||||
return static_cast<fl::i32>(absolute_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // No matching bit found
|
||||
}
|
||||
|
||||
/// Finds the first run of consecutive bits that match the test value.
|
||||
/// Returns the index of the first bit in the run, or -1 if no run found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param min_length Minimum length of the run (default: 1)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_run(bool test_value, fl::u32 min_length, fl::u32 offset = 0) const noexcept {
|
||||
fl::u32 run_start = offset;
|
||||
fl::u32 run_length = 0;
|
||||
|
||||
for (fl::u32 i = offset; i < N && run_length < min_length; ++i) {
|
||||
bool current_bit = test(i);
|
||||
if (current_bit != test_value) {
|
||||
run_length = 0;
|
||||
if (i + 1 < N) {
|
||||
run_start = i + 1;
|
||||
}
|
||||
} else {
|
||||
++run_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (run_length >= min_length) {
|
||||
return static_cast<fl::i32>(run_start);
|
||||
}
|
||||
|
||||
return -1; // No run found
|
||||
}
|
||||
|
||||
/// Friend operators for convenience.
|
||||
friend BitsetFixed operator&(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs &= rhs;
|
||||
}
|
||||
friend BitsetFixed operator|(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
friend BitsetFixed operator^(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs ^= rhs;
|
||||
}
|
||||
friend BitsetFixed operator~(BitsetFixed bs) noexcept { return bs.flip(); }
|
||||
};
|
||||
|
||||
/// A Bitset implementation with inline storage that can grow if needed.
|
||||
/// T is the storage type (u8, u16, u32, uint64_t)
|
||||
/// N is the initial number of bits to store inline
|
||||
template <fl::u32 N = 16> // Default size is 16 bits, or 2 bytes
|
||||
class BitsetInlined {
|
||||
private:
|
||||
// Either store a fixed Bitset<N> or a dynamic Bitset
|
||||
using fixed_bitset = BitsetFixed<N>;
|
||||
Variant<fixed_bitset, bitset_dynamic> _storage;
|
||||
|
||||
public:
|
||||
struct Proxy {
|
||||
BitsetInlined &_bitset;
|
||||
fl::u32 _pos;
|
||||
|
||||
Proxy(BitsetInlined &bitset, fl::u32 pos)
|
||||
: _bitset(bitset), _pos(pos) {}
|
||||
|
||||
Proxy &operator=(bool value) {
|
||||
_bitset.set(_pos, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return _bitset.test(_pos); }
|
||||
};
|
||||
|
||||
Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); }
|
||||
|
||||
/// Constructs a Bitset with all bits reset.
|
||||
BitsetInlined() : _storage(fixed_bitset()) {}
|
||||
BitsetInlined(fl::size size) : _storage(fixed_bitset()) {
|
||||
if (size > N) {
|
||||
_storage = bitset_dynamic(size);
|
||||
}
|
||||
}
|
||||
BitsetInlined(const BitsetInlined &other) : _storage(other._storage) {}
|
||||
BitsetInlined(BitsetInlined &&other) noexcept
|
||||
: _storage(fl::move(other._storage)) {}
|
||||
BitsetInlined &operator=(const BitsetInlined &other) {
|
||||
if (this != &other) {
|
||||
_storage = other._storage;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BitsetInlined &operator=(BitsetInlined &&other) noexcept {
|
||||
if (this != &other) {
|
||||
_storage = fl::move(other._storage);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Resets all bits to zero.
|
||||
void reset() noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->reset();
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void assign(fl::size n, bool value) {
|
||||
resize(n);
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->assign(n, value);
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->assign(n, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Resizes the Bitset if needed
|
||||
void resize(fl::u32 new_size) {
|
||||
if (new_size <= N) {
|
||||
// If we're already using the fixed Bitset, nothing to do
|
||||
if (_storage.template is<bitset_dynamic>()) {
|
||||
// Convert back to fixed Bitset
|
||||
fixed_bitset fixed;
|
||||
bitset_dynamic *dynamic =
|
||||
_storage.template ptr<bitset_dynamic>();
|
||||
|
||||
// Copy bits from dynamic to fixed
|
||||
for (fl::u32 i = 0; i < N && i < dynamic->size(); ++i) {
|
||||
if (dynamic->test(i)) {
|
||||
fixed.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
_storage = fixed;
|
||||
}
|
||||
} else {
|
||||
// Need to use dynamic Bitset
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
// Convert from fixed to dynamic
|
||||
bitset_dynamic dynamic(new_size);
|
||||
fixed_bitset *fixed = _storage.template ptr<fixed_bitset>();
|
||||
|
||||
// Copy bits from fixed to dynamic
|
||||
for (fl::u32 i = 0; i < N; ++i) {
|
||||
if (fixed->test(i)) {
|
||||
dynamic.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
_storage = dynamic;
|
||||
} else {
|
||||
// Already using dynamic, just resize
|
||||
_storage.template ptr<bitset_dynamic>()->resize(new_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears the bit at position pos.
|
||||
BitsetInlined &set(fl::u32 pos, bool value = true) {
|
||||
if (pos >= N && _storage.template is<fixed_bitset>()) {
|
||||
resize(pos + 1);
|
||||
}
|
||||
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
if (pos < N) {
|
||||
_storage.template ptr<fixed_bitset>()->set(pos, value);
|
||||
}
|
||||
} else {
|
||||
if (pos >= _storage.template ptr<bitset_dynamic>()->size()) {
|
||||
_storage.template ptr<bitset_dynamic>()->resize(pos + 1);
|
||||
}
|
||||
_storage.template ptr<bitset_dynamic>()->set(pos, value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Clears the bit at position pos.
|
||||
BitsetInlined &reset(fl::u32 pos) { return set(pos, false); }
|
||||
|
||||
/// Flips (toggles) the bit at position pos.
|
||||
BitsetInlined &flip(fl::u32 pos) {
|
||||
if (pos >= N && _storage.template is<fixed_bitset>()) {
|
||||
resize(pos + 1);
|
||||
}
|
||||
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
if (pos < N) {
|
||||
_storage.template ptr<fixed_bitset>()->flip(pos);
|
||||
}
|
||||
} else {
|
||||
if (pos >= _storage.template ptr<bitset_dynamic>()->size()) {
|
||||
_storage.template ptr<bitset_dynamic>()->resize(pos + 1);
|
||||
}
|
||||
_storage.template ptr<bitset_dynamic>()->flip(pos);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Flips all bits.
|
||||
BitsetInlined &flip() noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->flip();
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->flip();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Tests whether the bit at position pos is set.
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return pos < N ? _storage.template ptr<fixed_bitset>()->test(pos)
|
||||
: false;
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->test(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the bit at position pos.
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Returns the number of set bits.
|
||||
fl::u32 count() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->count();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->count();
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries.
|
||||
bool any() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->any();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->any();
|
||||
}
|
||||
}
|
||||
|
||||
bool none() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->none();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->none();
|
||||
}
|
||||
}
|
||||
|
||||
bool all() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->all();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of the Bitset (number of bits).
|
||||
fl::u32 size() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return N;
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->size();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert bitset to string representation
|
||||
void to_string(string* dst) const {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->to_string(dst);
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->to_string(dst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->find_first(test_value, offset);
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->find_first(test_value, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitwise operators
|
||||
friend BitsetInlined operator~(const BitsetInlined &bs) noexcept {
|
||||
BitsetInlined result = bs;
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator&(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() &=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 min_size =
|
||||
result.size() < rhs.size() ? result.size() : rhs.size();
|
||||
for (fl::u32 i = 0; i < min_size; ++i) {
|
||||
result.set(i, result.test(i) && rhs.test(i));
|
||||
}
|
||||
// Clear any bits beyond the size of rhs
|
||||
for (fl::u32 i = min_size; i < result.size(); ++i) {
|
||||
result.reset(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator|(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() |=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 max_size =
|
||||
result.size() > rhs.size() ? result.size() : rhs.size();
|
||||
|
||||
// Resize if needed
|
||||
if (result.size() < max_size) {
|
||||
result.resize(max_size);
|
||||
}
|
||||
|
||||
// Set bits from rhs
|
||||
for (fl::u32 i = 0; i < rhs.size(); ++i) {
|
||||
if (rhs.test(i)) {
|
||||
result.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator^(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() ^=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 max_size =
|
||||
result.size() > rhs.size() ? result.size() : rhs.size();
|
||||
|
||||
// Resize if needed
|
||||
if (result.size() < max_size) {
|
||||
result.resize(max_size);
|
||||
}
|
||||
|
||||
// XOR bits from rhs
|
||||
for (fl::u32 i = 0; i < rhs.size(); ++i) {
|
||||
result.set(i, result.test(i) != rhs.test(i));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
451
libraries/FastLED/src/fl/bitset_dynamic.h
Normal file
451
libraries/FastLED/src/fl/bitset_dynamic.h
Normal file
@@ -0,0 +1,451 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include <string.h> // for memcpy
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class string;
|
||||
|
||||
namespace detail {
|
||||
void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst);
|
||||
}
|
||||
|
||||
/// A dynamic bitset implementation that can be resized at runtime
|
||||
class bitset_dynamic {
|
||||
private:
|
||||
static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16);
|
||||
using block_type = fl::u16;
|
||||
|
||||
block_type *_blocks = nullptr;
|
||||
fl::u32 _block_count = 0;
|
||||
fl::u32 _size = 0;
|
||||
|
||||
// Helper to calculate block count from bit count
|
||||
static fl::u32 calc_block_count(fl::u32 bit_count) {
|
||||
return (bit_count + bits_per_block - 1) / bits_per_block;
|
||||
}
|
||||
|
||||
public:
|
||||
// Default constructor
|
||||
bitset_dynamic() = default;
|
||||
|
||||
// Constructor with initial size
|
||||
explicit bitset_dynamic(fl::u32 size) { resize(size); }
|
||||
|
||||
// Copy constructor
|
||||
bitset_dynamic(const bitset_dynamic &other) {
|
||||
if (other._size > 0) {
|
||||
resize(other._size);
|
||||
memcpy(_blocks, other._blocks, _block_count * sizeof(block_type));
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
bitset_dynamic(bitset_dynamic &&other) noexcept
|
||||
: _blocks(other._blocks), _block_count(other._block_count),
|
||||
_size(other._size) {
|
||||
other._blocks = nullptr;
|
||||
other._block_count = 0;
|
||||
other._size = 0;
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
bitset_dynamic &operator=(const bitset_dynamic &other) {
|
||||
if (this != &other) {
|
||||
if (other._size > 0) {
|
||||
resize(other._size);
|
||||
memcpy(_blocks, other._blocks,
|
||||
_block_count * sizeof(block_type));
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
bitset_dynamic &operator=(bitset_dynamic &&other) noexcept {
|
||||
if (this != &other) {
|
||||
delete[] _blocks;
|
||||
_blocks = other._blocks;
|
||||
_block_count = other._block_count;
|
||||
_size = other._size;
|
||||
other._blocks = nullptr;
|
||||
other._block_count = 0;
|
||||
other._size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~bitset_dynamic() { delete[] _blocks; }
|
||||
|
||||
// Assign n bits to the value specified
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void assign(fl::u32 n, bool value) {
|
||||
if (n > _size) {
|
||||
resize(n);
|
||||
}
|
||||
if (value) {
|
||||
// Set all bits to 1
|
||||
if (_blocks && _block_count > 0) {
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
_blocks[i] = static_cast<block_type>(~block_type(0));
|
||||
}
|
||||
// Clear any bits beyond the actual size
|
||||
if (_size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask = static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Set all bits to 0
|
||||
reset();
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Resize the bitset
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void resize(fl::u32 new_size) {
|
||||
if (new_size == _size)
|
||||
return;
|
||||
|
||||
fl::u32 new_block_count = (new_size + bits_per_block - 1) / bits_per_block;
|
||||
|
||||
if (new_block_count != _block_count) {
|
||||
block_type *new_blocks = new block_type[new_block_count];
|
||||
fl::memfill(new_blocks, 0, new_block_count * sizeof(block_type));
|
||||
|
||||
if (_blocks) {
|
||||
fl::u32 copy_blocks = MIN(_block_count, new_block_count);
|
||||
memcpy(new_blocks, _blocks, copy_blocks * sizeof(block_type));
|
||||
}
|
||||
|
||||
delete[] _blocks;
|
||||
_blocks = new_blocks;
|
||||
_block_count = new_block_count;
|
||||
}
|
||||
|
||||
_size = new_size;
|
||||
|
||||
// Clear any bits beyond the new size
|
||||
if (_blocks && _block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Clear the bitset (reset to empty)
|
||||
void clear() {
|
||||
delete[] _blocks;
|
||||
_blocks = nullptr;
|
||||
_block_count = 0;
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
// Reset all bits to 0
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void reset() noexcept {
|
||||
if (_blocks && _block_count > 0) {
|
||||
fl::memfill(_blocks, 0, _block_count * sizeof(block_type));
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Reset a specific bit to 0
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void reset(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] &= ~(static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Set a specific bit to 1
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void set(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] |= (static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Set a specific bit to a given value
|
||||
void set(fl::u32 pos, bool value) noexcept {
|
||||
if (value) {
|
||||
set(pos);
|
||||
} else {
|
||||
reset(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Flip a specific bit
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void flip(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] ^= (static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Flip all bits
|
||||
void flip() noexcept {
|
||||
if (!_blocks) return;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
_blocks[i] = ~_blocks[i];
|
||||
}
|
||||
|
||||
// Clear any bits beyond size
|
||||
if (_block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Test if a bit is set
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
return (_blocks[idx] >> off) & 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Count the number of set bits
|
||||
fl::u32 count() const noexcept {
|
||||
if (!_blocks) return 0;
|
||||
|
||||
fl::u32 result = 0;
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
result += static_cast<fl::u32>(__builtin_popcount(_blocks[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if any bit is set
|
||||
bool any() const noexcept {
|
||||
if (!_blocks) return false;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
if (_blocks[i] != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if no bit is set
|
||||
bool none() const noexcept { return !any(); }
|
||||
|
||||
// Check if all bits are set
|
||||
bool all() const noexcept {
|
||||
if (_size == 0)
|
||||
return true;
|
||||
|
||||
if (!_blocks) return false;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count - 1; ++i) {
|
||||
if (_blocks[i] != static_cast<block_type>(~block_type(0)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check last block with mask for valid bits
|
||||
if (_block_count > 0) {
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
return (_blocks[_block_count - 1] & mask) == mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the size of the bitset
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
fl::u32 size() const noexcept {
|
||||
// Note: _size is a member variable, not a pointer, so this should be safe
|
||||
// but we add this comment to clarify for static analysis
|
||||
return _size;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Convert bitset to string representation
|
||||
void to_string(string* dst) const;
|
||||
|
||||
// Access operator
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
// If offset is beyond our size, no match possible
|
||||
if (offset >= _size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate which block to start from
|
||||
fl::u32 start_block = offset / bits_per_block;
|
||||
fl::u32 start_bit = offset % bits_per_block;
|
||||
|
||||
for (fl::u32 block_idx = start_block; block_idx < _block_count; ++block_idx) {
|
||||
block_type current_block = _blocks[block_idx];
|
||||
|
||||
// For the last block, we need to mask out unused bits
|
||||
if (block_idx == _block_count - 1 && _size % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = _size % bits_per_block;
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: static_cast<block_type>(((block_type(1) << valid_bits) - 1));
|
||||
current_block &= mask;
|
||||
}
|
||||
|
||||
// If looking for false bits, invert the block
|
||||
if (!test_value) {
|
||||
current_block = ~current_block;
|
||||
}
|
||||
|
||||
// For the first block, mask out bits before the offset
|
||||
if (block_idx == start_block && start_bit > 0) {
|
||||
current_block &= ~static_cast<block_type>(((block_type(1) << start_bit) - 1));
|
||||
}
|
||||
|
||||
// If there are any matching bits in this block
|
||||
if (current_block != 0) {
|
||||
// Find the first set bit
|
||||
fl::u32 bit_pos = static_cast<fl::u32>(__builtin_ctz(current_block));
|
||||
fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos;
|
||||
|
||||
// Make sure we haven't gone past the end of the bitset
|
||||
if (absolute_pos < _size) {
|
||||
return static_cast<fl::i32>(absolute_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // No matching bit found
|
||||
}
|
||||
|
||||
// Bitwise AND operator
|
||||
bitset_dynamic operator&(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] & other._blocks[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise OR operator
|
||||
bitset_dynamic operator|(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] | other._blocks[i];
|
||||
}
|
||||
|
||||
// Copy remaining blocks from the larger bitset
|
||||
if (_block_count > min_blocks) {
|
||||
memcpy(result._blocks + min_blocks, _blocks + min_blocks,
|
||||
(_block_count - min_blocks) * sizeof(block_type));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise XOR operator
|
||||
bitset_dynamic operator^(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] ^ other._blocks[i];
|
||||
}
|
||||
|
||||
// Copy remaining blocks from the larger bitset
|
||||
if (_block_count > min_blocks) {
|
||||
memcpy(result._blocks + min_blocks, _blocks + min_blocks,
|
||||
(_block_count - min_blocks) * sizeof(block_type));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise NOT operator
|
||||
bitset_dynamic operator~() const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
result._blocks[i] = ~_blocks[i];
|
||||
}
|
||||
|
||||
// Clear any bits beyond size
|
||||
if (_block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
result._blocks[last_block_idx] &= mask;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
134
libraries/FastLED/src/fl/blur.cpp
Normal file
134
libraries/FastLED/src/fl/blur.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/blur.h"
|
||||
#include "fl/colorutils_misc.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/deprecated.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "lib8tion/scale8.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Legacy XY function. This is a weak symbol that can be overridden by the user.
|
||||
fl::u16 XY(fl::u8 x, fl::u8 y) FL_LINK_WEAK;
|
||||
|
||||
FL_LINK_WEAK fl::u16 XY(fl::u8 x, fl::u8 y) {
|
||||
FASTLED_UNUSED(x);
|
||||
FASTLED_UNUSED(y);
|
||||
FASTLED_ASSERT(false, "the user didn't provide an XY function");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fl::u16 XY(fl::u8 x, fl::u8 y) {
|
||||
// return 0;
|
||||
// }
|
||||
// make this a weak symbol
|
||||
namespace {
|
||||
fl::u16 xy_legacy_wrapper(fl::u16 x, fl::u16 y, fl::u16 width,
|
||||
fl::u16 height) {
|
||||
FASTLED_UNUSED(width);
|
||||
FASTLED_UNUSED(height);
|
||||
return XY(x, y);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
|
||||
// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors.
|
||||
//
|
||||
// 0 = no spread at all
|
||||
// 64 = moderate spreading
|
||||
// 172 = maximum smooth, even spreading
|
||||
//
|
||||
// 173..255 = wider spreading, but increasing flicker
|
||||
//
|
||||
// Total light is NOT entirely conserved, so many repeated
|
||||
// calls to 'blur' will also result in the light fading,
|
||||
// eventually all the way to black; this is by design so that
|
||||
// it can be used to (slowly) clear the LEDs to black.
|
||||
void blur1d(CRGB *leds, fl::u16 numLeds, fract8 blur_amount) {
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u16 i = 0; i < numLeds; ++i) {
|
||||
CRGB cur = leds[i];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[i - 1] += part;
|
||||
leds[i] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xymap) {
|
||||
blurRows(leds, width, height, blur_amount, xymap);
|
||||
blurColumns(leds, width, height, blur_amount, xymap);
|
||||
}
|
||||
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount) {
|
||||
XYMap xy =
|
||||
XYMap::constructWithUserFunction(width, height, xy_legacy_wrapper);
|
||||
blur2d(leds, width, height, blur_amount, xy);
|
||||
}
|
||||
|
||||
void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xyMap) {
|
||||
|
||||
/* for( fl::u8 row = 0; row < height; row++) {
|
||||
CRGB* rowbase = leds + (row * width);
|
||||
blur1d( rowbase, width, blur_amount);
|
||||
}
|
||||
*/
|
||||
// blur rows same as columns, for irregular matrix
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
for (fl::u8 row = 0; row < height; row++) {
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u8 i = 0; i < width; i++) {
|
||||
CRGB cur = leds[xyMap.mapToIndex(i, row)];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[xyMap.mapToIndex(i - 1, row)] += part;
|
||||
leds[xyMap.mapToIndex(i, row)] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// blurColumns: perform a blur1d on each column of a rectangular matrix
|
||||
void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xyMap) {
|
||||
// blur columns
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
for (fl::u8 col = 0; col < width; ++col) {
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u8 i = 0; i < height; ++i) {
|
||||
CRGB cur = leds[xyMap.mapToIndex(col, i)];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[xyMap.mapToIndex(col, i - 1)] += part;
|
||||
leds[xyMap.mapToIndex(col, i)] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
71
libraries/FastLED/src/fl/blur.h
Normal file
71
libraries/FastLED/src/fl/blur.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/int.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/deprecated.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @defgroup ColorBlurs Color Blurring Functions
|
||||
/// Functions for blurring colors
|
||||
/// @{
|
||||
|
||||
/// One-dimensional blur filter.
|
||||
/// Spreads light to 2 line neighbors.
|
||||
/// * 0 = no spread at all
|
||||
/// * 64 = moderate spreading
|
||||
/// * 172 = maximum smooth, even spreading
|
||||
/// * 173..255 = wider spreading, but increasing flicker
|
||||
///
|
||||
/// Total light is NOT entirely conserved, so many repeated
|
||||
/// calls to 'blur' will also result in the light fading,
|
||||
/// eventually all the way to black; this is by design so that
|
||||
/// it can be used to (slowly) clear the LEDs to black.
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param numLeds the number of LEDs to blur
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blur1d(CRGB *leds, u16 numLeds, fract8 blur_amount);
|
||||
|
||||
/// Two-dimensional blur filter.
|
||||
/// Spreads light to 8 XY neighbors.
|
||||
/// * 0 = no spread at all
|
||||
/// * 64 = moderate spreading
|
||||
/// * 172 = maximum smooth, even spreading
|
||||
/// * 173..255 = wider spreading, but increasing flicker
|
||||
///
|
||||
/// Total light is NOT entirely conserved, so many repeated
|
||||
/// calls to 'blur' will also result in the light fading,
|
||||
/// eventually all the way to black; this is by design so that
|
||||
/// it can be used to (slowly) clear the LEDs to black.
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param width the width of the matrix
|
||||
/// @param height the height of the matrix
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// Legacy version of blur2d, which does not require an XYMap but instead
|
||||
/// implicitly binds to XY() function. If you are hitting a linker error here,
|
||||
/// then use blur2d(..., const fl::XYMap& xymap) instead.
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount)
|
||||
FASTLED_DEPRECATED("Use blur2d(..., const fl::XYMap& xymap) instead");
|
||||
|
||||
/// Perform a blur1d() on every row of a rectangular matrix
|
||||
/// @see blur1d()
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param width the width of the matrix
|
||||
/// @param height the height of the matrix
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// Perform a blur1d() on every column of a rectangular matrix
|
||||
/// @copydetails blurRows()
|
||||
void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// @} ColorBlurs
|
||||
|
||||
} // namespace fl
|
||||
29
libraries/FastLED/src/fl/bytestream.h
Normal file
29
libraries/FastLED/src/fl/bytestream.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(ByteStream);
|
||||
|
||||
// An abstract class that represents a stream of bytes.
|
||||
class ByteStream {
|
||||
public:
|
||||
virtual ~ByteStream() {}
|
||||
virtual bool available(fl::size) const = 0;
|
||||
virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0;
|
||||
virtual const char *path() const = 0;
|
||||
virtual void close() {} // default is do nothing on close.
|
||||
// convenience functions
|
||||
virtual fl::size readCRGB(CRGB *dst, fl::size n) {
|
||||
return read((fl::u8 *)dst, n * 3) / 3;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
72
libraries/FastLED/src/fl/bytestreammemory.cpp
Normal file
72
libraries/FastLED/src/fl/bytestreammemory.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <string.h>
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/bytestreammemory.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
ByteStreamMemory::ByteStreamMemory(fl::u32 size_buffer)
|
||||
: mReadBuffer(size_buffer) {}
|
||||
|
||||
ByteStreamMemory::~ByteStreamMemory() = default;
|
||||
|
||||
bool ByteStreamMemory::available(fl::size n) const {
|
||||
return mReadBuffer.size() >= n;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::read(fl::u8 *dst, fl::size bytesToRead) {
|
||||
if (!available(bytesToRead) || dst == nullptr) {
|
||||
FASTLED_WARN("ByteStreamMemory::read: !available(bytesToRead): "
|
||||
<< bytesToRead
|
||||
<< " mReadBuffer.size(): " << mReadBuffer.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
fl::size actualBytesToRead = MIN(bytesToRead, mReadBuffer.size());
|
||||
fl::size bytesRead = 0;
|
||||
|
||||
while (bytesRead < actualBytesToRead) {
|
||||
fl::u8 &b = dst[bytesRead];
|
||||
mReadBuffer.pop_front(&b);
|
||||
bytesRead++;
|
||||
}
|
||||
|
||||
if (bytesRead == 0) {
|
||||
FASTLED_WARN("ByteStreamMemory::read: bytesRead == 0");
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::write(const fl::u8 *src, fl::size n) {
|
||||
if (src == nullptr || mReadBuffer.capacity() == 0) {
|
||||
FASTLED_WARN_IF(src == nullptr,
|
||||
"ByteStreamMemory::write: src == nullptr");
|
||||
FASTLED_WARN_IF(mReadBuffer.capacity() == 0,
|
||||
"ByteStreamMemory::write: mReadBuffer.capacity() == 0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fl::size written = 0;
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (mReadBuffer.full()) {
|
||||
FASTLED_WARN("ByteStreamMemory::write: mReadBuffer.full(): "
|
||||
<< mReadBuffer.size());
|
||||
break;
|
||||
}
|
||||
mReadBuffer.push_back(src[i]);
|
||||
++written;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::writeCRGB(const CRGB *src, fl::size n) {
|
||||
fl::size bytes_written = write(reinterpret_cast<const fl::u8 *>(src), n * 3);
|
||||
fl::size pixels_written = bytes_written / 3;
|
||||
return pixels_written;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/bytestreammemory.h
Normal file
31
libraries/FastLED/src/fl/bytestreammemory.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
|
||||
#include "fl/bytestream.h"
|
||||
#include "fl/circular_buffer.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(ByteStreamMemory);
|
||||
|
||||
class ByteStreamMemory : public ByteStream {
|
||||
public:
|
||||
ByteStreamMemory(fl::u32 size_buffer);
|
||||
~ByteStreamMemory() override;
|
||||
bool available(fl::size n) const override;
|
||||
fl::size read(fl::u8 *dst, fl::size bytesToRead) override;
|
||||
void clear() { mReadBuffer.clear(); }
|
||||
const char *path() const override { return "ByteStreamMemory"; }
|
||||
fl::size write(const fl::u8 *src, fl::size n);
|
||||
fl::size writeCRGB(const CRGB *src, fl::size n);
|
||||
|
||||
private:
|
||||
CircularBuffer<fl::u8> mReadBuffer;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
143
libraries/FastLED/src/fl/circular_buffer.h
Normal file
143
libraries/FastLED/src/fl/circular_buffer.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/scoped_array.h"
|
||||
#include "fl/stdint.h" // For standard integer types
|
||||
|
||||
namespace fl {
|
||||
|
||||
// TODO:
|
||||
// ERROR bit to indicate over flow.
|
||||
|
||||
// Static version with compile-time capacity
|
||||
template <typename T, fl::size N>
|
||||
class StaticCircularBuffer {
|
||||
public:
|
||||
StaticCircularBuffer() : mHead(0), mTail(0) {}
|
||||
|
||||
void push(const T &value) {
|
||||
if (full()) {
|
||||
mTail = (mTail + 1) % (N + 1); // Overwrite the oldest element
|
||||
}
|
||||
mBuffer[mHead] = value;
|
||||
mHead = (mHead + 1) % (N + 1);
|
||||
}
|
||||
|
||||
bool pop(T &value) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
value = mBuffer[mTail];
|
||||
mTail = (mTail + 1) % (N + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
fl::size size() const { return (mHead + N + 1 - mTail) % (N + 1); }
|
||||
constexpr fl::size capacity() const { return N; }
|
||||
bool empty() const { return mHead == mTail; }
|
||||
bool full() const { return ((mHead + 1) % (N + 1)) == mTail; }
|
||||
void clear() { mHead = mTail = 0; }
|
||||
|
||||
private:
|
||||
T mBuffer[N + 1]; // Extra space for distinguishing full/empty
|
||||
fl::size mHead;
|
||||
fl::size mTail;
|
||||
};
|
||||
|
||||
// Dynamic version with runtime capacity (existing implementation)
|
||||
template <typename T> class DynamicCircularBuffer {
|
||||
public:
|
||||
DynamicCircularBuffer(fl::size capacity)
|
||||
: mCapacity(capacity + 1), mHead(0),
|
||||
mTail(0) { // Extra space for distinguishing full/empty
|
||||
mBuffer.reset(new T[mCapacity]);
|
||||
}
|
||||
|
||||
DynamicCircularBuffer(const DynamicCircularBuffer &) = delete;
|
||||
DynamicCircularBuffer &operator=(const DynamicCircularBuffer &) = delete;
|
||||
|
||||
bool push_back(const T &value) {
|
||||
if (full()) {
|
||||
mTail = increment(mTail); // Overwrite the oldest element
|
||||
}
|
||||
mBuffer[mHead] = value;
|
||||
mHead = increment(mHead);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop_front(T *dst = nullptr) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
if (dst) {
|
||||
*dst = mBuffer[mTail];
|
||||
}
|
||||
mTail = increment(mTail);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push_front(const T &value) {
|
||||
if (full()) {
|
||||
mHead = decrement(mHead); // Overwrite the oldest element
|
||||
}
|
||||
mTail = decrement(mTail);
|
||||
mBuffer[mTail] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop_back(T *dst = nullptr) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
mHead = decrement(mHead);
|
||||
if (dst) {
|
||||
*dst = mBuffer[mHead];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
T &front() { return mBuffer[mTail]; }
|
||||
|
||||
const T &front() const { return mBuffer[mTail]; }
|
||||
|
||||
T &back() { return mBuffer[(mHead + mCapacity - 1) % mCapacity]; }
|
||||
|
||||
const T &back() const {
|
||||
return mBuffer[(mHead + mCapacity - 1) % mCapacity];
|
||||
}
|
||||
|
||||
T &operator[](fl::size index) { return mBuffer[(mTail + index) % mCapacity]; }
|
||||
|
||||
const T &operator[](fl::size index) const {
|
||||
return mBuffer[(mTail + index) % mCapacity];
|
||||
}
|
||||
|
||||
fl::size size() const { return (mHead + mCapacity - mTail) % mCapacity; }
|
||||
|
||||
fl::size capacity() const { return mCapacity - 1; }
|
||||
|
||||
bool empty() const { return mHead == mTail; }
|
||||
|
||||
bool full() const { return increment(mHead) == mTail; }
|
||||
|
||||
void clear() { mHead = mTail = 0; }
|
||||
|
||||
private:
|
||||
fl::size increment(fl::size index) const { return (index + 1) % mCapacity; }
|
||||
|
||||
fl::size decrement(fl::size index) const {
|
||||
return (index + mCapacity - 1) % mCapacity;
|
||||
}
|
||||
|
||||
fl::scoped_array<T> mBuffer;
|
||||
fl::size mCapacity;
|
||||
fl::size mHead;
|
||||
fl::size mTail;
|
||||
};
|
||||
|
||||
// For backward compatibility, keep the old name for the dynamic version
|
||||
template <typename T>
|
||||
using CircularBuffer = DynamicCircularBuffer<T>;
|
||||
|
||||
} // namespace fl
|
||||
20
libraries/FastLED/src/fl/clamp.h
Normal file
20
libraries/FastLED/src/fl/clamp.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/force_inline.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T> FASTLED_FORCE_INLINE T clamp(T value, T min, T max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
39
libraries/FastLED/src/fl/clear.h
Normal file
39
libraries/FastLED/src/fl/clear.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/leds.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template<typename T>
|
||||
class Grid;
|
||||
|
||||
// Memory safe clear function for CRGB arrays.
|
||||
template <int N> inline void clear(CRGB (&arr)[N]) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
arr[i] = CRGB::Black;
|
||||
}
|
||||
}
|
||||
|
||||
inline void clear(Leds &leds) { leds.fill(CRGB::Black); }
|
||||
|
||||
template<fl::size W, fl::size H>
|
||||
inline void clear(LedsXY<W, H> &leds) {
|
||||
leds.fill(CRGB::Black);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void clear(Grid<T> &grid) {
|
||||
grid.clear();
|
||||
}
|
||||
|
||||
// Default, when you don't know what do then call clear.
|
||||
template<typename Container>
|
||||
inline void clear(Container &container) {
|
||||
container.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
1109
libraries/FastLED/src/fl/colorutils.cpp
Normal file
1109
libraries/FastLED/src/fl/colorutils.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1807
libraries/FastLED/src/fl/colorutils.h
Normal file
1807
libraries/FastLED/src/fl/colorutils.h
Normal file
File diff suppressed because it is too large
Load Diff
41
libraries/FastLED/src/fl/colorutils_misc.h
Normal file
41
libraries/FastLED/src/fl/colorutils_misc.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
// TODO: Figure out how to namespace these.
|
||||
typedef fl::u32 TProgmemRGBPalette16[16]; ///< CRGBPalette16 entries stored in
|
||||
///< PROGMEM memory
|
||||
typedef fl::u32 TProgmemHSVPalette16[16]; ///< CHSVPalette16 entries stored in
|
||||
///< PROGMEM memory
|
||||
/// Alias for TProgmemRGBPalette16
|
||||
#define TProgmemPalette16 TProgmemRGBPalette16
|
||||
typedef fl::u32 TProgmemRGBPalette32[32]; ///< CRGBPalette32 entries stored in
|
||||
///< PROGMEM memory
|
||||
typedef fl::u32 TProgmemHSVPalette32[32]; ///< CHSVPalette32 entries stored in
|
||||
///< PROGMEM memory
|
||||
/// Alias for TProgmemRGBPalette32
|
||||
#define TProgmemPalette32 TProgmemRGBPalette32
|
||||
|
||||
/// Byte of an RGB gradient, stored in PROGMEM memory
|
||||
typedef const fl::u8 TProgmemRGBGradientPalette_byte;
|
||||
/// Pointer to bytes of an RGB gradient, stored in PROGMEM memory
|
||||
/// @see DEFINE_GRADIENT_PALETTE
|
||||
/// @see DECLARE_GRADIENT_PALETTE
|
||||
typedef const TProgmemRGBGradientPalette_byte *TProgmemRGBGradientPalette_bytes;
|
||||
/// Alias of ::TProgmemRGBGradientPalette_bytes
|
||||
typedef TProgmemRGBGradientPalette_bytes TProgmemRGBGradientPaletteRef;
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// Hue direction for calculating fill gradients.
|
||||
/// Since "hue" is a value around a color wheel, there are always two directions
|
||||
/// to sweep from one hue to another.
|
||||
typedef enum {
|
||||
FORWARD_HUES, ///< Hue always goes clockwise around the color wheel
|
||||
BACKWARD_HUES, ///< Hue always goes counter-clockwise around the color wheel
|
||||
SHORTEST_HUES, ///< Hue goes whichever way is shortest
|
||||
LONGEST_HUES ///< Hue goes whichever way is longest
|
||||
} TGradientDirectionCode;
|
||||
|
||||
} // namespace fl
|
||||
9
libraries/FastLED/src/fl/comparators.h
Normal file
9
libraries/FastLED/src/fl/comparators.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/utility.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// DefaultLess is now an alias for less<T>, defined in utility.h
|
||||
|
||||
} // namespace fl
|
||||
146
libraries/FastLED/src/fl/compiler_control.h
Normal file
146
libraries/FastLED/src/fl/compiler_control.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
// Stringify helper for pragma arguments
|
||||
#define FL_STRINGIFY2(x) #x
|
||||
#define FL_STRINGIFY(x) FL_STRINGIFY2(x)
|
||||
|
||||
// BEGIN BASE MACROS
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_PUSH _Pragma("clang diagnostic push")
|
||||
#define FL_DISABLE_WARNING_POP _Pragma("clang diagnostic pop")
|
||||
// Usage: FL_DISABLE_WARNING(float-equal)
|
||||
#define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(clang diagnostic ignored "-W" #warning))
|
||||
|
||||
#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406
|
||||
#define FL_DISABLE_WARNING_PUSH _Pragma("GCC diagnostic push")
|
||||
#define FL_DISABLE_WARNING_POP _Pragma("GCC diagnostic pop")
|
||||
// Usage: FL_DISABLE_WARNING(float-equal)
|
||||
#define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(GCC diagnostic ignored "-W" #warning))
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_PUSH
|
||||
#define FL_DISABLE_WARNING_POP
|
||||
#define FL_DISABLE_WARNING(warning)
|
||||
#endif
|
||||
// END BASE MACROS
|
||||
|
||||
// WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL.
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED \
|
||||
FL_DISABLE_WARNING(self-assign-overloaded)
|
||||
// Clang doesn't have format-truncation warning, use no-op
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE FL_DISABLE_WARNING(null-dereference)
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION FL_DISABLE_WARNING(implicit-int-conversion)
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION FL_DISABLE_WARNING(float-conversion)
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION FL_DISABLE_WARNING(sign-conversion)
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32 FL_DISABLE_WARNING(shorten-64-to-32)
|
||||
#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406
|
||||
// GCC doesn't have global-constructors warning, use no-op
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
// GCC doesn't have self-assign-overloaded warning, use no-op
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED
|
||||
// GCC has format-truncation warning
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION \
|
||||
FL_DISABLE_WARNING(format-truncation)
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER \
|
||||
FL_DISABLE_WARNING(unused-parameter)
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE \
|
||||
FL_DISABLE_WARNING(return-type)
|
||||
|
||||
// implicit-fallthrough warning requires GCC >= 7.0
|
||||
#if (__GNUC__*100 + __GNUC_MINOR__) >= 700
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH FL_DISABLE_WARNING(implicit-fallthrough)
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH
|
||||
#endif
|
||||
// GCC doesn't support these conversion warnings on older versions
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
// GCC doesn't have shorten-64-to-32 warning, use no-op
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
|
||||
#endif
|
||||
|
||||
// END WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL.
|
||||
|
||||
// Fast math optimization controls with additional aggressive flags
|
||||
#if defined(__clang__)
|
||||
#define FL_FAST_MATH_BEGIN \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("STDC FP_CONTRACT ON")
|
||||
|
||||
#define FL_FAST_MATH_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_FAST_MATH_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"fast-math\")") \
|
||||
_Pragma("GCC optimize (\"tree-vectorize\")") \
|
||||
_Pragma("GCC optimize (\"unroll-loops\")")
|
||||
|
||||
#define FL_FAST_MATH_END _Pragma("GCC pop_options")
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
#define FL_FAST_MATH_BEGIN __pragma(float_control(precise, off))
|
||||
#define FL_FAST_MATH_END __pragma(float_control(precise, on))
|
||||
#else
|
||||
#define FL_FAST_MATH_BEGIN /* nothing */
|
||||
#define FL_FAST_MATH_END /* nothing */
|
||||
#endif
|
||||
|
||||
// Optimization Level O3
|
||||
#if defined(__clang__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN \
|
||||
_Pragma("clang diagnostic push")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"O3\")")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("GCC pop_options")
|
||||
#else
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN /* nothing */
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END /* nothing */
|
||||
#endif
|
||||
|
||||
// Optimization Level O0 (Debug/No optimization)
|
||||
#if defined(__clang__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN \
|
||||
_Pragma("clang diagnostic push")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"O0\")")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("GCC pop_options")
|
||||
#else
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN /* nothing */
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END /* nothing */
|
||||
#endif
|
||||
|
||||
#ifndef FL_LINK_WEAK
|
||||
#define FL_LINK_WEAK __attribute__((weak))
|
||||
#endif
|
||||
15
libraries/FastLED/src/fl/convert.h
Normal file
15
libraries/FastLED/src/fl/convert.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/int.h"
|
||||
// Conversion from FastLED timings to the type found in datasheets.
|
||||
inline void convert_fastled_timings_to_timedeltas(fl::u16 T1, fl::u16 T2,
|
||||
fl::u16 T3, fl::u16 *T0H,
|
||||
fl::u16 *T0L, fl::u16 *T1H,
|
||||
fl::u16 *T1L) {
|
||||
*T0H = T1;
|
||||
*T0L = T2 + T3;
|
||||
*T1H = T1 + T2;
|
||||
*T1L = T3;
|
||||
}
|
||||
502
libraries/FastLED/src/fl/corkscrew.cpp
Normal file
502
libraries/FastLED/src/fl/corkscrew.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/splat.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/map_range.h"
|
||||
#include "fl/leds.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
// New helper function to calculate individual LED position
|
||||
vec2f calculateLedPositionExtended(fl::u16 ledIndex, fl::u16 numLeds, float totalTurns, const Gap& gapParams, fl::u16 width, fl::u16 height) {
|
||||
FL_UNUSED(height);
|
||||
FL_UNUSED(totalTurns);
|
||||
|
||||
// Check if gap feature is active AND will actually be triggered
|
||||
bool gapActive = (gapParams.num_leds > 0 && gapParams.gap > 0.0f && numLeds > static_cast<fl::u16>(gapParams.num_leds));
|
||||
|
||||
if (!gapActive) {
|
||||
// Original behavior when no gap or gap never triggers
|
||||
const float ledProgress = static_cast<float>(ledIndex) / static_cast<float>(numLeds - 1);
|
||||
const fl::u16 row = ledIndex / width;
|
||||
const fl::u16 remainder = ledIndex % width;
|
||||
const float alpha = static_cast<float>(remainder) / static_cast<float>(width);
|
||||
const float width_pos = ledProgress * numLeds;
|
||||
const float height_pos = static_cast<float>(row) + alpha;
|
||||
return vec2f(width_pos, height_pos);
|
||||
}
|
||||
|
||||
// Simplified gap calculation based on user expectation
|
||||
// User wants: LED0=0, LED1=3, LED2=6(wraps to 0) with width=3
|
||||
// This suggests they want regular spacing of width units per LED
|
||||
|
||||
// Simple spacing: each LED is separated by exactly width units
|
||||
float width_pos = static_cast<float>(ledIndex) * static_cast<float>(width);
|
||||
|
||||
// For height, divide by width to get turn progress
|
||||
float height_pos = width_pos / static_cast<float>(width);
|
||||
|
||||
return vec2f(width_pos, height_pos);
|
||||
}
|
||||
|
||||
void calculateDimensions(float totalTurns, fl::u16 numLeds, const Gap& gapParams, fl::u16 *width, fl::u16 *height) {
|
||||
FL_UNUSED(gapParams);
|
||||
|
||||
// Calculate optimal width and height
|
||||
float ledsPerTurn = static_cast<float>(numLeds) / totalTurns;
|
||||
fl::u16 calc_width = static_cast<fl::u16>(fl::ceil(ledsPerTurn));
|
||||
|
||||
fl::u16 height_from_turns = static_cast<fl::u16>(fl::ceil(totalTurns));
|
||||
fl::u16 calc_height;
|
||||
|
||||
// If the grid would have more pixels than LEDs, adjust height to better match
|
||||
if (calc_width * height_from_turns > numLeds) {
|
||||
// Calculate height that better matches LED count
|
||||
calc_height = static_cast<fl::u16>(fl::ceil(static_cast<float>(numLeds) / static_cast<float>(calc_width)));
|
||||
} else {
|
||||
calc_height = height_from_turns;
|
||||
}
|
||||
|
||||
*width = calc_width;
|
||||
*height = calc_height;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// New primary constructor
|
||||
Corkscrew::Corkscrew(float totalTurns, fl::u16 numLeds, bool invert, const Gap& gapParams)
|
||||
: mTotalTurns(totalTurns), mNumLeds(numLeds), mGapParams(gapParams), mInvert(invert) {
|
||||
fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
|
||||
mOwnsPixels = false;
|
||||
}
|
||||
|
||||
// Constructor with external pixel buffer
|
||||
Corkscrew::Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert, const Gap& gapParams)
|
||||
: mTotalTurns(totalTurns), mNumLeds(static_cast<fl::u16>(dstPixels.size())),
|
||||
mGapParams(gapParams), mInvert(invert) {
|
||||
fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
|
||||
mPixelStorage = dstPixels;
|
||||
mOwnsPixels = false; // External span
|
||||
}
|
||||
|
||||
|
||||
|
||||
vec2f Corkscrew::at_no_wrap(fl::u16 i) const {
|
||||
if (i >= mNumLeds) {
|
||||
// Handle out-of-bounds access, possibly by returning a default value
|
||||
return vec2f(0, 0);
|
||||
}
|
||||
|
||||
// Compute position on-the-fly
|
||||
vec2f position = calculateLedPositionExtended(i, mNumLeds, mTotalTurns,
|
||||
mGapParams, mWidth, mHeight);
|
||||
|
||||
// // Apply inversion if requested
|
||||
// if (mInvert) {
|
||||
// fl::u16 invertedIndex = mNumLeds - 1 - i;
|
||||
// position = calculateLedPositionExtended(invertedIndex, mNumLeds, mTotalTurns,
|
||||
// mGapParams, mState.width, mState.height);
|
||||
// }
|
||||
|
||||
// now wrap the x-position
|
||||
//position.x = fmodf(position.x, static_cast<float>(mState.width));
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
vec2f Corkscrew::at_exact(fl::u16 i) const {
|
||||
// Get the unwrapped position
|
||||
vec2f position = at_no_wrap(i);
|
||||
|
||||
// Apply cylindrical wrapping to the x-position (like at_wrap does)
|
||||
position.x = fmodf(position.x, static_cast<float>(mWidth));
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const {
|
||||
if (i >= mNumLeds) {
|
||||
// Handle out-of-bounds access, possibly by returning a default
|
||||
// Tile2x2_u8
|
||||
FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: "
|
||||
<< i << " size: " << mNumLeds);
|
||||
return Tile2x2_u8();
|
||||
}
|
||||
|
||||
// Use the splat function to convert the vec2f to a Tile2x2_u8
|
||||
float i_floor = floorf(i);
|
||||
float i_ceil = ceilf(i);
|
||||
if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) {
|
||||
// If the index is the same, just return the splat of that index
|
||||
vec2f position = at_no_wrap(static_cast<fl::u16>(i_floor));
|
||||
return splat(position);
|
||||
} else {
|
||||
// Interpolate between the two points and return the splat of the result
|
||||
vec2f pos1 = at_no_wrap(static_cast<fl::u16>(i_floor));
|
||||
vec2f pos2 = at_no_wrap(static_cast<fl::u16>(i_ceil));
|
||||
float t = i - i_floor;
|
||||
vec2f interpolated_pos = map_range(t, 0.0f, 1.0f, pos1, pos2);
|
||||
return splat(interpolated_pos);
|
||||
}
|
||||
}
|
||||
|
||||
fl::size Corkscrew::size() const { return mNumLeds; }
|
||||
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const {
|
||||
if (mCachingEnabled) {
|
||||
// Use cache if enabled
|
||||
initializeCache();
|
||||
|
||||
// Convert float index to integer for cache lookup
|
||||
fl::size cache_index = static_cast<fl::size>(i);
|
||||
if (cache_index < mTileCache.size()) {
|
||||
return mTileCache[cache_index];
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to dynamic calculation if cache disabled or index out of bounds
|
||||
return calculateTileAtWrap(i);
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::calculateTileAtWrap(float i) const {
|
||||
// This is a splatted pixel, but wrapped around the cylinder.
|
||||
// This is useful for rendering the corkscrew in a cylindrical way.
|
||||
Tile2x2_u8 tile = at_splat_extrapolate(i);
|
||||
Tile2x2_u8_wrap::Entry data[2][2];
|
||||
vec2<u16> origin = tile.origin();
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
// For each pixel in the tile, map it to the cylinder so that each subcomponent
|
||||
// is mapped to the correct position on the cylinder.
|
||||
vec2<u16> pos = origin + vec2<u16>(x, y);
|
||||
// now wrap the x-position
|
||||
pos.x = fmodf(pos.x, static_cast<float>(mWidth));
|
||||
data[x][y] = {pos, tile.at(x, y)};
|
||||
}
|
||||
}
|
||||
return Tile2x2_u8_wrap(data);
|
||||
}
|
||||
|
||||
void Corkscrew::setCachingEnabled(bool enabled) {
|
||||
if (!enabled && mCachingEnabled) {
|
||||
// Caching was enabled, now disabling - clear the cache
|
||||
mTileCache.clear();
|
||||
mCacheInitialized = false;
|
||||
}
|
||||
mCachingEnabled = enabled;
|
||||
}
|
||||
|
||||
void Corkscrew::initializeCache() const {
|
||||
if (!mCacheInitialized && mCachingEnabled) {
|
||||
// Initialize cache with tiles for each LED position
|
||||
mTileCache.resize(mNumLeds);
|
||||
|
||||
// Populate cache lazily
|
||||
for (fl::size i = 0; i < mNumLeds; ++i) {
|
||||
mTileCache[i] = calculateTileAtWrap(static_cast<float>(i));
|
||||
}
|
||||
|
||||
mCacheInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
CRGB* Corkscrew::rawData() {
|
||||
// Use variant storage if available, otherwise fall back to input surface
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>().data();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
return mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>().data();
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input surface data
|
||||
auto surface = getOrCreateInputSurface();
|
||||
return surface->data();
|
||||
}
|
||||
|
||||
fl::span<CRGB> Corkscrew::data() {
|
||||
// Use variant storage if available, otherwise fall back to input surface
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
auto& vec = mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>();
|
||||
return fl::span<CRGB>(vec.data(), vec.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input surface data as span
|
||||
auto surface = getOrCreateInputSurface();
|
||||
return fl::span<CRGB>(surface->data(), surface->size());
|
||||
}
|
||||
|
||||
|
||||
void Corkscrew::readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling) {
|
||||
|
||||
if (use_multi_sampling) {
|
||||
readFromMulti(source_grid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the input surface
|
||||
auto target_surface = getOrCreateInputSurface();
|
||||
|
||||
// Clear surface first
|
||||
target_surface->clear();
|
||||
|
||||
// Iterate through each LED in the corkscrew
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the rectangular coordinates for this corkscrew LED
|
||||
vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
|
||||
|
||||
// Convert to integer coordinates for indexing
|
||||
vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
|
||||
static_cast<fl::i16>(rect_pos.y + 0.5f));
|
||||
|
||||
// Clamp coordinates to grid bounds
|
||||
coord.x = MAX(0, MIN(coord.x, static_cast<fl::i16>(source_grid.width()) - 1));
|
||||
coord.y = MAX(0, MIN(coord.y, static_cast<fl::i16>(source_grid.height()) - 1));
|
||||
|
||||
// Sample from the source fl::Grid using its at() method
|
||||
CRGB sampled_color = source_grid.at(coord.x, coord.y);
|
||||
|
||||
// Store the sampled color directly in the target surface
|
||||
if (led_idx < target_surface->size()) {
|
||||
target_surface->data()[led_idx] = sampled_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::clear() {
|
||||
// Clear input surface if it exists
|
||||
if (mInputSurface) {
|
||||
mInputSurface->clear();
|
||||
mInputSurface.reset(); // Free the shared_ptr memory
|
||||
}
|
||||
|
||||
// Clear pixel storage if we own it (vector variant)
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
auto& vec = mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>();
|
||||
vec.clear();
|
||||
// Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
|
||||
}
|
||||
// Note: Don't clear external spans as we don't own that memory
|
||||
}
|
||||
|
||||
// Clear tile cache
|
||||
mTileCache.clear();
|
||||
// Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
|
||||
mCacheInitialized = false;
|
||||
}
|
||||
|
||||
void Corkscrew::fillInputSurface(const CRGB& color) {
|
||||
auto target_surface = getOrCreateInputSurface();
|
||||
for (fl::size i = 0; i < target_surface->size(); ++i) {
|
||||
target_surface->data()[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::draw(bool use_multi_sampling) {
|
||||
// The draw method should map from the rectangular surface to the LED pixel data
|
||||
// This is the reverse of readFrom - we read from our surface and populate LED data
|
||||
auto source_surface = getOrCreateInputSurface();
|
||||
|
||||
// Make sure we have pixel storage
|
||||
if (mPixelStorage.empty()) {
|
||||
// If no pixel storage is configured, there's nothing to draw to
|
||||
return;
|
||||
}
|
||||
|
||||
CRGB* led_data = rawData();
|
||||
if (!led_data) return;
|
||||
|
||||
if (use_multi_sampling) {
|
||||
// Use multi-sampling to get better accuracy
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the wrapped tile for this LED position
|
||||
Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
|
||||
|
||||
// Accumulate color from the 4 sample points with their weights
|
||||
fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
|
||||
fl::u32 total_weight = 0;
|
||||
|
||||
// Sample from each of the 4 corners of the tile
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
const auto& entry = tile.at(x, y);
|
||||
vec2<u16> pos = entry.first;
|
||||
fl::u8 weight = entry.second;
|
||||
|
||||
// Bounds check for the source surface
|
||||
if (pos.x < source_surface->width() && pos.y < source_surface->height()) {
|
||||
// Sample from the source surface
|
||||
CRGB sample_color = source_surface->at(pos.x, pos.y);
|
||||
|
||||
// Accumulate weighted color components
|
||||
r_accum += static_cast<fl::u32>(sample_color.r) * weight;
|
||||
g_accum += static_cast<fl::u32>(sample_color.g) * weight;
|
||||
b_accum += static_cast<fl::u32>(sample_color.b) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final color by dividing by total weight
|
||||
CRGB final_color = CRGB::Black;
|
||||
if (total_weight > 0) {
|
||||
final_color.r = static_cast<fl::u8>(r_accum / total_weight);
|
||||
final_color.g = static_cast<fl::u8>(g_accum / total_weight);
|
||||
final_color.b = static_cast<fl::u8>(b_accum / total_weight);
|
||||
}
|
||||
|
||||
// Store the result in the LED data
|
||||
led_data[led_idx] = final_color;
|
||||
}
|
||||
} else {
|
||||
// Simple non-multi-sampling version
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the rectangular coordinates for this corkscrew LED
|
||||
vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
|
||||
|
||||
// Convert to integer coordinates for indexing
|
||||
vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
|
||||
static_cast<fl::i16>(rect_pos.y + 0.5f));
|
||||
|
||||
// Clamp coordinates to surface bounds
|
||||
coord.x = MAX(0, MIN(coord.x, static_cast<fl::i16>(source_surface->width()) - 1));
|
||||
coord.y = MAX(0, MIN(coord.y, static_cast<fl::i16>(source_surface->height()) - 1));
|
||||
|
||||
// Sample from the source surface
|
||||
CRGB sampled_color = source_surface->at(coord.x, coord.y);
|
||||
|
||||
// Store the sampled color in the LED data
|
||||
led_data[led_idx] = sampled_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::readFromMulti(const fl::Grid<CRGB>& source_grid) const {
|
||||
// Get the target surface and clear it
|
||||
auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
|
||||
target_surface->clear();
|
||||
const u16 width = static_cast<u16>(source_grid.width());
|
||||
const u16 height = static_cast<u16>(source_grid.height());
|
||||
|
||||
// Iterate through each LED in the corkscrew
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the wrapped tile for this LED position
|
||||
Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
|
||||
|
||||
// Accumulate color from the 4 sample points with their weights
|
||||
fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
|
||||
fl::u32 total_weight = 0;
|
||||
|
||||
// Sample from each of the 4 corners of the tile
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
const auto& entry = tile.at(x, y);
|
||||
vec2<u16> pos = entry.first; // position is the first element of the pair
|
||||
fl::u8 weight = entry.second; // weight is the second element of the pair
|
||||
|
||||
// Bounds check for the source grid
|
||||
if (pos.x >= 0 && pos.x < width &&
|
||||
pos.y >= 0 && pos.y < height) {
|
||||
|
||||
// Sample from the source grid
|
||||
CRGB sample_color = source_grid.at(pos.x, pos.y);
|
||||
|
||||
// Accumulate weighted color components
|
||||
r_accum += static_cast<fl::u32>(sample_color.r) * weight;
|
||||
g_accum += static_cast<fl::u32>(sample_color.g) * weight;
|
||||
b_accum += static_cast<fl::u32>(sample_color.b) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final color by dividing by total weight
|
||||
CRGB final_color = CRGB::Black;
|
||||
if (total_weight > 0) {
|
||||
final_color.r = static_cast<fl::u8>(r_accum / total_weight);
|
||||
final_color.g = static_cast<fl::u8>(g_accum / total_weight);
|
||||
final_color.b = static_cast<fl::u8>(b_accum / total_weight);
|
||||
}
|
||||
|
||||
// Store the result in the target surface at the LED index position
|
||||
auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
|
||||
if (led_idx < target_surface->size()) {
|
||||
target_surface->data()[led_idx] = final_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator implementation
|
||||
vec2f Corkscrew::iterator::operator*() const {
|
||||
return corkscrew_->at_no_wrap(static_cast<fl::u16>(position_));
|
||||
}
|
||||
|
||||
fl::ScreenMap Corkscrew::toScreenMap(float diameter) const {
|
||||
// Create a ScreenMap with the correct number of LEDs
|
||||
fl::ScreenMap screenMap(mNumLeds, diameter);
|
||||
|
||||
// For each LED index, calculate its position and set it in the ScreenMap
|
||||
for (fl::u16 i = 0; i < mNumLeds; ++i) {
|
||||
// Get the wrapped 2D position for this LED index in the cylindrical mapping
|
||||
vec2f position = at_exact(i);
|
||||
|
||||
// Set the wrapped position in the ScreenMap
|
||||
screenMap.set(i, position);
|
||||
}
|
||||
|
||||
return screenMap;
|
||||
}
|
||||
|
||||
// Enhanced surface handling methods
|
||||
fl::shared_ptr<fl::Grid<CRGB>>& Corkscrew::getOrCreateInputSurface() {
|
||||
if (!mInputSurface) {
|
||||
// Create a new Grid with cylinder dimensions using PSRAM allocation
|
||||
mInputSurface = fl::make_shared<fl::Grid<CRGB>>(mWidth, mHeight);
|
||||
}
|
||||
return mInputSurface;
|
||||
}
|
||||
|
||||
fl::Grid<CRGB>& Corkscrew::surface() {
|
||||
return *getOrCreateInputSurface();
|
||||
}
|
||||
|
||||
|
||||
fl::size Corkscrew::pixelCount() const {
|
||||
// Use variant storage if available, otherwise fall back to legacy buffer size
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>().size();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
return mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>().size();
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input size
|
||||
return mNumLeds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
251
libraries/FastLED/src/fl/corkscrew.h
Normal file
251
libraries/FastLED/src/fl/corkscrew.h
Normal file
@@ -0,0 +1,251 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file corkscrew.h
|
||||
* @brief Corkscrew LED strip projection and rendering
|
||||
*
|
||||
* The Corkscrew class provides a complete solution for drawing to densely-wrapped
|
||||
* helical LED strips. It maps a cylindrical coordinate system to a linear LED
|
||||
* strip, allowing you to draw on a rectangular surface and have it correctly
|
||||
* projected onto the corkscrew topology.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Create a Corkscrew with the number of turns and LEDs
|
||||
* 2. Draw patterns on the input surface using surface()
|
||||
* 3. Call draw() to map the surface to LED pixels
|
||||
* 4. Access the final LED data via rawData()
|
||||
*
|
||||
* The class handles:
|
||||
* - Automatic cylindrical dimension calculation
|
||||
* - Pixel storage (external span or internal allocation)
|
||||
* - Multi-sampling for smooth projections
|
||||
* - Gap compensation for non-continuous wrapping
|
||||
* - Iterator interface for advanced coordinate access
|
||||
*
|
||||
* Parameters:
|
||||
* - totalTurns: Number of helical turns around the cylinder
|
||||
* - numLeds: Total number of LEDs in the strip
|
||||
* - invert: Reverse the mapping direction (default: false)
|
||||
* - gapParams: Optional gap compensation for solder points in a strip.
|
||||
*/
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/span.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class Leds;
|
||||
class ScreenMap;
|
||||
template<typename T> class Grid;
|
||||
|
||||
// Simple constexpr functions for compile-time corkscrew dimension calculation
|
||||
constexpr fl::u16 calculateCorkscrewWidth(float totalTurns, fl::u16 numLeds) {
|
||||
return static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / totalTurns));
|
||||
}
|
||||
|
||||
constexpr fl::u16 calculateCorkscrewHeight(float totalTurns, fl::u16 numLeds) {
|
||||
return (calculateCorkscrewWidth(totalTurns, numLeds) * static_cast<int>(ceil_constexpr(totalTurns)) > numLeds) ?
|
||||
static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / static_cast<float>(calculateCorkscrewWidth(totalTurns, numLeds)))) :
|
||||
static_cast<fl::u16>(ceil_constexpr(totalTurns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Struct representing gap parameters for corkscrew mapping
|
||||
*/
|
||||
struct Gap {
|
||||
int num_leds = 0; // Number of LEDs after which gap is activated, 0 = no gap
|
||||
float gap = 0.0f; // Gap value from 0 to 1, represents percentage of width unit to add
|
||||
|
||||
Gap() = default;
|
||||
Gap(float g) : num_leds(0), gap(g) {} // Backwards compatibility constructor
|
||||
Gap(int n, float g) : num_leds(n), gap(g) {} // New constructor with num_leds
|
||||
|
||||
// Rule of 5 for POD data
|
||||
Gap(const Gap &other) = default;
|
||||
Gap &operator=(const Gap &other) = default;
|
||||
Gap(Gap &&other) noexcept = default;
|
||||
Gap &operator=(Gap &&other) noexcept = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
|
||||
// a densly wrapped LED corkscrew.
|
||||
class Corkscrew {
|
||||
public:
|
||||
|
||||
// Pixel storage variants - can hold either external span or owned vector
|
||||
using PixelStorage = fl::Variant<fl::span<CRGB>, fl::vector<CRGB, fl::allocator_psram<CRGB>>>;
|
||||
|
||||
// Iterator class moved from CorkscrewState
|
||||
class iterator {
|
||||
public:
|
||||
using value_type = vec2f;
|
||||
using difference_type = fl::i32;
|
||||
using pointer = vec2f *;
|
||||
using reference = vec2f &;
|
||||
|
||||
iterator(const Corkscrew *corkscrew, fl::size position)
|
||||
: corkscrew_(corkscrew), position_(position) {}
|
||||
|
||||
vec2f operator*() const;
|
||||
|
||||
iterator &operator++() {
|
||||
++position_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator temp = *this;
|
||||
++position_;
|
||||
return temp;
|
||||
}
|
||||
|
||||
iterator &operator--() {
|
||||
--position_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) {
|
||||
iterator temp = *this;
|
||||
--position_;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const {
|
||||
return position_ == other.position_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const {
|
||||
return position_ != other.position_;
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const {
|
||||
return static_cast<difference_type>(position_) -
|
||||
static_cast<difference_type>(other.position_);
|
||||
}
|
||||
|
||||
private:
|
||||
const Corkscrew *corkscrew_;
|
||||
fl::size position_;
|
||||
};
|
||||
|
||||
// Constructors that integrate input parameters directly
|
||||
// Primary constructor with default values for invert and gapParams
|
||||
Corkscrew(float totalTurns, fl::u16 numLeds, bool invert = false, const Gap& gapParams = Gap());
|
||||
|
||||
// Constructor with external pixel buffer - these pixels will be drawn to directly
|
||||
Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert = false, const Gap& gapParams = Gap());
|
||||
|
||||
|
||||
Corkscrew(const Corkscrew &) = default;
|
||||
Corkscrew(Corkscrew &&) = default;
|
||||
|
||||
|
||||
// Caching control
|
||||
void setCachingEnabled(bool enabled);
|
||||
|
||||
// Essential API - Core functionality
|
||||
fl::u16 cylinderWidth() const { return mWidth; }
|
||||
fl::u16 cylinderHeight() const { return mHeight; }
|
||||
|
||||
// Enhanced surface handling with shared_ptr
|
||||
// Note: Input surface will be created on first call
|
||||
fl::shared_ptr<fl::Grid<CRGB>>& getOrCreateInputSurface();
|
||||
|
||||
// Draw like a regular rectangle surface - access input surface directly
|
||||
fl::Grid<CRGB>& surface();
|
||||
|
||||
|
||||
// Draw the corkscrew by reading from the internal surface and populating LED pixels
|
||||
void draw(bool use_multi_sampling = true);
|
||||
|
||||
// Pixel storage access - works with both external and owned pixels
|
||||
// This represents the pixels that will be drawn after draw() is called
|
||||
CRGB* rawData();
|
||||
|
||||
// Returns span of pixels that will be written to when draw() is called
|
||||
fl::span<CRGB> data();
|
||||
|
||||
fl::size pixelCount() const;
|
||||
// Create and return a fully constructed ScreenMap for this corkscrew
|
||||
// Each LED index will be mapped to its exact position on the cylindrical surface
|
||||
fl::ScreenMap toScreenMap(float diameter = 0.5f) const;
|
||||
|
||||
// STL-style container interface
|
||||
fl::size size() const;
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
iterator end() { return iterator(this, size()); }
|
||||
|
||||
// Non-essential API - Lower level access
|
||||
vec2f at_no_wrap(fl::u16 i) const;
|
||||
vec2f at_exact(fl::u16 i) const;
|
||||
Tile2x2_u8_wrap at_wrap(float i) const;
|
||||
|
||||
// Clear all buffers and free memory
|
||||
void clear();
|
||||
|
||||
// Fill the input surface with a color
|
||||
void fillInputSurface(const CRGB& color);
|
||||
|
||||
|
||||
private:
|
||||
// For internal use. Splats the pixel on the surface which
|
||||
// extends past the width. This extended Tile2x2 is designed
|
||||
// to be wrapped around with a Tile2x2_u8_wrap.
|
||||
Tile2x2_u8 at_splat_extrapolate(float i) const;
|
||||
|
||||
// Read from fl::Grid<CRGB> object and populate our internal rectangular buffer
|
||||
// by sampling from the XY coordinates mapped to each corkscrew LED position
|
||||
// use_multi_sampling = true will use multi-sampling to sample from the source grid,
|
||||
// this will give a little bit better accuracy and the screenmap will be more accurate.
|
||||
void readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling = true);
|
||||
|
||||
// Read from rectangular buffer using multi-sampling and store in target grid
|
||||
// Uses Tile2x2_u8_wrap for sub-pixel accurate sampling with proper blending
|
||||
void readFromMulti(const fl::Grid<CRGB>& target_grid) const;
|
||||
|
||||
// Initialize the rectangular buffer if not already done
|
||||
void initializeBuffer() const;
|
||||
|
||||
// Initialize the cache if not already done and caching is enabled
|
||||
void initializeCache() const;
|
||||
|
||||
// Calculate the tile at position i without using cache
|
||||
Tile2x2_u8_wrap calculateTileAtWrap(float i) const;
|
||||
|
||||
// Core corkscrew parameters (moved from CorkscrewInput)
|
||||
float mTotalTurns = 19.0f; // Total turns of the corkscrew
|
||||
fl::u16 mNumLeds = 144; // Number of LEDs
|
||||
Gap mGapParams; // Gap parameters for gap accounting
|
||||
bool mInvert = false; // If true, reverse the mapping order
|
||||
|
||||
// Cylindrical mapping dimensions (moved from CorkscrewState)
|
||||
fl::u16 mWidth = 0; // Width of cylindrical map (circumference of one turn)
|
||||
fl::u16 mHeight = 0; // Height of cylindrical map (total vertical segments)
|
||||
|
||||
// Enhanced pixel storage - variant supports both external and owned pixels
|
||||
PixelStorage mPixelStorage;
|
||||
bool mOwnsPixels = false; // Track whether we own the pixel data
|
||||
|
||||
// Input surface for drawing operations
|
||||
fl::shared_ptr<fl::Grid<CRGB>> mInputSurface;
|
||||
|
||||
// Caching for Tile2x2_u8_wrap objects
|
||||
mutable fl::vector<Tile2x2_u8_wrap> mTileCache;
|
||||
mutable bool mCacheInitialized = false;
|
||||
bool mCachingEnabled = true; // Default to enabled
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/crgb_hsv16.cpp
Normal file
31
libraries/FastLED/src/fl/crgb_hsv16.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/// @file crgb_hsv16.cpp
|
||||
/// HSV16-dependent methods for CRGB - only linked when HSV16 functionality is used
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "crgb.h"
|
||||
#include "fl/hsv16.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
CRGB CRGB::colorBoost(fl::EaseType saturation_function, fl::EaseType luminance_function) const {
|
||||
fl::HSV16 hsv(*this);
|
||||
return hsv.colorBoost(saturation_function, luminance_function);
|
||||
}
|
||||
|
||||
void CRGB::colorBoost(const CRGB* src, CRGB* dst, size_t count, fl::EaseType saturation_function, fl::EaseType luminance_function) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
dst[i] = src[i].colorBoost(saturation_function, luminance_function);
|
||||
}
|
||||
}
|
||||
|
||||
fl::HSV16 CRGB::toHSV16() const {
|
||||
return fl::HSV16(*this);
|
||||
}
|
||||
|
||||
// Constructor implementation for HSV16 -> CRGB automatic conversion
|
||||
CRGB::CRGB(const fl::HSV16& rhs) {
|
||||
*this = rhs.ToRGB();
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
25
libraries/FastLED/src/fl/cstddef.h
Normal file
25
libraries/FastLED/src/fl/cstddef.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
#include <cstddef> // ok include
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
// FastLED equivalent of std::nullptr_t
|
||||
typedef decltype(nullptr) nullptr_t;
|
||||
|
||||
// FastLED equivalent of std::size_t and std::ptrdiff_t
|
||||
// These are defined here for completeness but may already exist elsewhere
|
||||
#ifndef FL_SIZE_T_DEFINED
|
||||
#define FL_SIZE_T_DEFINED
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
#endif
|
||||
|
||||
#ifndef FL_PTRDIFF_T_DEFINED
|
||||
#define FL_PTRDIFF_T_DEFINED
|
||||
typedef __PTRDIFF_TYPE__ ptrdiff_t;
|
||||
#endif
|
||||
|
||||
} // namespace fl
|
||||
67
libraries/FastLED/src/fl/dbg.h
Normal file
67
libraries/FastLED/src/fl/dbg.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/strstream.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
// Forward declaration to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled
|
||||
// This prevents ~5KB memory bloat for simple applications
|
||||
#ifndef FL_DBG_PRINTLN_DECLARED
|
||||
#define FL_DBG_PRINTLN_DECLARED
|
||||
namespace fl {
|
||||
void println(const char* str);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
// ".build/src/fl/dbg.h" -> "src/fl/dbg.h"
|
||||
// "blah/blah/blah.h" -> "blah.h"
|
||||
inline const char *fastled_file_offset(const char *file) {
|
||||
const char *p = file;
|
||||
const char *last_slash = nullptr;
|
||||
|
||||
while (*p) {
|
||||
if (p[0] == 's' && p[1] == 'r' && p[2] == 'c' && p[3] == '/') {
|
||||
return p; // Skip past "src/"
|
||||
}
|
||||
if (*p == '/') { // fallback to using last slash
|
||||
last_slash = p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
// If "src/" not found but we found at least one slash, return after the
|
||||
// last slash
|
||||
if (last_slash) {
|
||||
return last_slash + 1;
|
||||
}
|
||||
return file; // If no slashes found at all, return original path
|
||||
}
|
||||
} // namespace fl
|
||||
|
||||
#if __EMSCRIPTEN__ || !defined(RELEASE) || defined(FASTLED_TESTING)
|
||||
#define FASTLED_FORCE_DBG 1
|
||||
#endif
|
||||
|
||||
// Debug printing: Enable only when explicitly requested to avoid ~5KB memory bloat
|
||||
#if !defined(FASTLED_FORCE_DBG) || !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// By default, debug printing is disabled to prevent memory bloat in simple applications
|
||||
#define FASTLED_HAS_DBG 0
|
||||
#define _FASTLED_DGB(X) do { if (false) { fl::println(""); } } while(0) // No-op that handles << operator
|
||||
#else
|
||||
// Explicit debug mode enabled - uses fl::println()
|
||||
#define FASTLED_HAS_DBG 1
|
||||
#define _FASTLED_DGB(X) \
|
||||
fl::println( \
|
||||
(fl::StrStream() << (fl::fastled_file_offset(__FILE__)) \
|
||||
<< "(" << int(__LINE__) << "): " << X) \
|
||||
.c_str())
|
||||
#endif
|
||||
|
||||
#define FASTLED_DBG(X) _FASTLED_DGB(X)
|
||||
|
||||
#ifndef FASTLED_DBG_IF
|
||||
#define FASTLED_DBG_IF(COND, MSG) \
|
||||
if (COND) \
|
||||
FASTLED_DBG(MSG)
|
||||
#endif // FASTLED_DBG_IF
|
||||
28
libraries/FastLED/src/fl/deprecated.h
Normal file
28
libraries/FastLED/src/fl/deprecated.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__clang__)
|
||||
// Clang: Do not mark classes as deprecated
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#endif
|
||||
#elif defined(__GNUC__) // GCC (but not Clang)
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#define FASTLED_DEPRECATED_CLASS(msg) __attribute__((deprecated(msg)))
|
||||
#else
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated))
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#endif
|
||||
#endif
|
||||
#else // Other compilers
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#define FASTLED_DEPRECATED(msg)
|
||||
#endif
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#endif
|
||||
|
||||
|
||||
#define FL_DEPRECATED(msg) FASTLED_DEPRECATED(msg)
|
||||
#define FL_DEPRECATED_CLASS(msg) FASTLED_DEPRECATED_CLASS(msg)
|
||||
390
libraries/FastLED/src/fl/deque.h
Normal file
390
libraries/FastLED/src/fl/deque.h
Normal file
@@ -0,0 +1,390 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/initializer_list.h"
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/type_traits.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T, typename Allocator = fl::allocator<T>>
|
||||
class deque {
|
||||
private:
|
||||
T* mData = nullptr;
|
||||
fl::size mCapacity = 0;
|
||||
fl::size mSize = 0;
|
||||
fl::size mFront = 0; // Index of the front element
|
||||
Allocator mAlloc;
|
||||
|
||||
static const fl::size kInitialCapacity = 8;
|
||||
|
||||
void ensure_capacity(fl::size min_capacity) {
|
||||
if (mCapacity >= min_capacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
fl::size new_capacity = mCapacity == 0 ? kInitialCapacity : mCapacity * 2;
|
||||
while (new_capacity < min_capacity) {
|
||||
new_capacity *= 2;
|
||||
}
|
||||
|
||||
T* new_data = mAlloc.allocate(new_capacity);
|
||||
if (!new_data) {
|
||||
return; // Allocation failed
|
||||
}
|
||||
|
||||
// Copy existing elements to new buffer in linear order
|
||||
for (fl::size i = 0; i < mSize; ++i) {
|
||||
fl::size old_idx = (mFront + i) % mCapacity;
|
||||
mAlloc.construct(&new_data[i], fl::move(mData[old_idx]));
|
||||
mAlloc.destroy(&mData[old_idx]);
|
||||
}
|
||||
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
|
||||
mData = new_data;
|
||||
mCapacity = new_capacity;
|
||||
mFront = 0; // Reset front to 0 after reallocation
|
||||
}
|
||||
|
||||
fl::size get_index(fl::size logical_index) const {
|
||||
return (mFront + logical_index) % mCapacity;
|
||||
}
|
||||
|
||||
public:
|
||||
// Iterator implementation
|
||||
class iterator {
|
||||
private:
|
||||
deque* mDeque;
|
||||
fl::size mIndex;
|
||||
|
||||
public:
|
||||
iterator(deque* dq, fl::size index) : mDeque(dq), mIndex(index) {}
|
||||
|
||||
T& operator*() const {
|
||||
return (*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return &(*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
++mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator temp = *this;
|
||||
++mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
iterator& operator--() {
|
||||
--mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) {
|
||||
iterator temp = *this;
|
||||
--mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) const {
|
||||
return mDeque == other.mDeque && mIndex == other.mIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
class const_iterator {
|
||||
private:
|
||||
const deque* mDeque;
|
||||
fl::size mIndex;
|
||||
|
||||
public:
|
||||
const_iterator(const deque* dq, fl::size index) : mDeque(dq), mIndex(index) {}
|
||||
|
||||
const T& operator*() const {
|
||||
return (*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
const T* operator->() const {
|
||||
return &(*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
const_iterator& operator++() {
|
||||
++mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator++(int) {
|
||||
const_iterator temp = *this;
|
||||
++mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
const_iterator& operator--() {
|
||||
--mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator--(int) {
|
||||
const_iterator temp = *this;
|
||||
--mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& other) const {
|
||||
return mDeque == other.mDeque && mIndex == other.mIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const const_iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Constructors
|
||||
deque() : mData(nullptr), mCapacity(0), mSize(0), mFront(0) {}
|
||||
|
||||
explicit deque(fl::size count, const T& value = T()) : deque() {
|
||||
resize(count, value);
|
||||
}
|
||||
|
||||
deque(const deque& other) : deque() {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
deque(deque&& other) : deque() {
|
||||
*this = fl::move(other);
|
||||
}
|
||||
|
||||
deque(fl::initializer_list<T> init) : deque() {
|
||||
for (const auto& value : init) {
|
||||
push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~deque() {
|
||||
clear();
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
// Assignment operators
|
||||
deque& operator=(const deque& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
for (fl::size i = 0; i < other.size(); ++i) {
|
||||
push_back(other[i]);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
deque& operator=(deque&& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
|
||||
mData = other.mData;
|
||||
mCapacity = other.mCapacity;
|
||||
mSize = other.mSize;
|
||||
mFront = other.mFront;
|
||||
mAlloc = other.mAlloc;
|
||||
|
||||
other.mData = nullptr;
|
||||
other.mCapacity = 0;
|
||||
other.mSize = 0;
|
||||
other.mFront = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Element access
|
||||
T& operator[](fl::size index) {
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
const T& operator[](fl::size index) const {
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
T& at(fl::size index) {
|
||||
if (index >= mSize) {
|
||||
// Handle bounds error - in embedded context, we'll just return the first element
|
||||
// In a real implementation, this might throw an exception
|
||||
return mData[mFront];
|
||||
}
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
const T& at(fl::size index) const {
|
||||
if (index >= mSize) {
|
||||
// Handle bounds error - in embedded context, we'll just return the first element
|
||||
return mData[mFront];
|
||||
}
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
T& front() {
|
||||
return mData[mFront];
|
||||
}
|
||||
|
||||
const T& front() const {
|
||||
return mData[mFront];
|
||||
}
|
||||
|
||||
T& back() {
|
||||
return mData[get_index(mSize - 1)];
|
||||
}
|
||||
|
||||
const T& back() const {
|
||||
return mData[get_index(mSize - 1)];
|
||||
}
|
||||
|
||||
// Iterators
|
||||
iterator begin() {
|
||||
return iterator(this, 0);
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return const_iterator(this, 0);
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator(this, mSize);
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return const_iterator(this, mSize);
|
||||
}
|
||||
|
||||
// Capacity
|
||||
bool empty() const {
|
||||
return mSize == 0;
|
||||
}
|
||||
|
||||
fl::size size() const {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
fl::size capacity() const {
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
void clear() {
|
||||
while (!empty()) {
|
||||
pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(const T& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
fl::size back_index = get_index(mSize);
|
||||
mAlloc.construct(&mData[back_index], value);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_back(T&& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
fl::size back_index = get_index(mSize);
|
||||
mAlloc.construct(&mData[back_index], fl::move(value));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(const T& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
mFront = (mFront - 1 + mCapacity) % mCapacity;
|
||||
mAlloc.construct(&mData[mFront], value);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(T&& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
mFront = (mFront - 1 + mCapacity) % mCapacity;
|
||||
mAlloc.construct(&mData[mFront], fl::move(value));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
if (mSize > 0) {
|
||||
fl::size back_index = get_index(mSize - 1);
|
||||
mAlloc.destroy(&mData[back_index]);
|
||||
--mSize;
|
||||
}
|
||||
}
|
||||
|
||||
void pop_front() {
|
||||
if (mSize > 0) {
|
||||
mAlloc.destroy(&mData[mFront]);
|
||||
mFront = (mFront + 1) % mCapacity;
|
||||
--mSize;
|
||||
}
|
||||
}
|
||||
|
||||
void resize(fl::size new_size) {
|
||||
resize(new_size, T());
|
||||
}
|
||||
|
||||
void resize(fl::size new_size, const T& value) {
|
||||
if (new_size > mSize) {
|
||||
// Add elements
|
||||
ensure_capacity(new_size);
|
||||
while (mSize < new_size) {
|
||||
push_back(value);
|
||||
}
|
||||
} else if (new_size < mSize) {
|
||||
// Remove elements
|
||||
while (mSize > new_size) {
|
||||
pop_back();
|
||||
}
|
||||
}
|
||||
// If new_size == mSize, do nothing
|
||||
}
|
||||
|
||||
void swap(deque& other) {
|
||||
if (this != &other) {
|
||||
T* temp_data = mData;
|
||||
fl::size temp_capacity = mCapacity;
|
||||
fl::size temp_size = mSize;
|
||||
fl::size temp_front = mFront;
|
||||
Allocator temp_alloc = mAlloc;
|
||||
|
||||
mData = other.mData;
|
||||
mCapacity = other.mCapacity;
|
||||
mSize = other.mSize;
|
||||
mFront = other.mFront;
|
||||
mAlloc = other.mAlloc;
|
||||
|
||||
other.mData = temp_data;
|
||||
other.mCapacity = temp_capacity;
|
||||
other.mSize = temp_size;
|
||||
other.mFront = temp_front;
|
||||
other.mAlloc = temp_alloc;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Convenience typedef for the most common use case
|
||||
typedef deque<int> deque_int;
|
||||
typedef deque<float> deque_float;
|
||||
typedef deque<double> deque_double;
|
||||
|
||||
} // namespace fl
|
||||
53
libraries/FastLED/src/fl/dll.h
Normal file
53
libraries/FastLED/src/fl/dll.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
/// @file dll.h
|
||||
/// FastLED dynamic library interface - lightweight header for external callers
|
||||
|
||||
#ifndef FASTLED_BUILD_EXPORTS
|
||||
#define FASTLED_BUILD_EXPORTS 0
|
||||
#endif
|
||||
|
||||
#if FASTLED_BUILD_EXPORTS
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Call the sketch's setup() function
|
||||
/// @note This is the C ABI export for external sketch runners
|
||||
FASTLED_EXPORT void sketch_setup(void);
|
||||
|
||||
/// Call the sketch's loop() function
|
||||
/// @note This is the C ABI export for external sketch runners
|
||||
FASTLED_EXPORT void sketch_loop(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// ================================================================================================
|
||||
// IMPLEMENTATIONS (when building FastLED as shared library)
|
||||
// ================================================================================================
|
||||
|
||||
#ifdef FASTLED_LIBRARY_SHARED
|
||||
|
||||
#ifdef __cplusplus
|
||||
// Forward declarations - provided by sketch
|
||||
extern void setup();
|
||||
extern void loop();
|
||||
|
||||
// Provide implementations for the exported functions
|
||||
FASTLED_EXPORT void sketch_setup() {
|
||||
setup();
|
||||
}
|
||||
|
||||
FASTLED_EXPORT void sketch_loop() {
|
||||
loop();
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // FASTLED_LIBRARY_SHARED
|
||||
|
||||
#endif // FASTLED_BUILD_EXPORTS
|
||||
188
libraries/FastLED/src/fl/downscale.cpp
Normal file
188
libraries/FastLED/src/fl/downscale.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
// BETA - NOT TESTED!!!
|
||||
// VIBE CODED WITH AI
|
||||
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshift-count-overflow"
|
||||
|
||||
namespace fl {
|
||||
|
||||
void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight,
|
||||
CRGB *dst) {
|
||||
fl::u16 dstWidth = srcWidth / 2;
|
||||
fl::u16 dstHeight = srcHeight / 2;
|
||||
|
||||
for (fl::u16 y = 0; y < dstHeight; ++y) {
|
||||
for (fl::u16 x = 0; x < dstWidth; ++x) {
|
||||
// Map to top-left of the 2x2 block in source
|
||||
fl::u16 srcX = x * 2;
|
||||
fl::u16 srcY = y * 2;
|
||||
|
||||
// Fetch 2x2 block
|
||||
const CRGB &p00 = src[(srcY)*srcWidth + (srcX)];
|
||||
const CRGB &p10 = src[(srcY)*srcWidth + (srcX + 1)];
|
||||
const CRGB &p01 = src[(srcY + 1) * srcWidth + (srcX)];
|
||||
const CRGB &p11 = src[(srcY + 1) * srcWidth + (srcX + 1)];
|
||||
|
||||
// Average each color channel
|
||||
fl::u16 r =
|
||||
(p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
|
||||
fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
|
||||
fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
|
||||
|
||||
// Store result
|
||||
dst[y * dstWidth + x] = CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
fl::u16 dstWidth = dstXY.getWidth();
|
||||
fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
FASTLED_ASSERT(srcXY.getWidth() == dstXY.getWidth() * 2,
|
||||
"Source width must be double the destination width");
|
||||
FASTLED_ASSERT(srcXY.getHeight() == dstXY.getHeight() * 2,
|
||||
"Source height must be double the destination height");
|
||||
|
||||
for (fl::u16 y = 0; y < dstHeight; ++y) {
|
||||
for (fl::u16 x = 0; x < dstWidth; ++x) {
|
||||
// Map to top-left of the 2x2 block in source
|
||||
fl::u16 srcX = x * 2;
|
||||
fl::u16 srcY = y * 2;
|
||||
|
||||
// Fetch 2x2 block
|
||||
const CRGB &p00 = src[srcXY.mapToIndex(srcX, srcY)];
|
||||
const CRGB &p10 = src[srcXY.mapToIndex(srcX + 1, srcY)];
|
||||
const CRGB &p01 = src[srcXY.mapToIndex(srcX, srcY + 1)];
|
||||
const CRGB &p11 = src[srcXY.mapToIndex(srcX + 1, srcY + 1)];
|
||||
|
||||
// Average each color channel
|
||||
fl::u16 r =
|
||||
(p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
|
||||
fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
|
||||
fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
|
||||
|
||||
// Store result
|
||||
dst[dstXY.mapToIndex(x, y)] =
|
||||
CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
const fl::u16 srcWidth = srcXY.getWidth();
|
||||
const fl::u16 srcHeight = srcXY.getHeight();
|
||||
const fl::u16 dstWidth = dstXY.getWidth();
|
||||
const fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
const fl::u32 FP_ONE = 256; // Q8.8 fixed-point multiplier
|
||||
|
||||
FASTLED_ASSERT(dstWidth <= srcWidth,
|
||||
"Destination width must be <= source width");
|
||||
FASTLED_ASSERT(dstHeight <= srcHeight,
|
||||
"Destination height must be <= source height");
|
||||
|
||||
for (fl::u16 dy = 0; dy < dstHeight; ++dy) {
|
||||
// Fractional boundaries in Q8.8
|
||||
fl::u32 dstY0 = (dy * srcHeight * FP_ONE) / dstHeight;
|
||||
fl::u32 dstY1 = ((dy + 1) * srcHeight * FP_ONE) / dstHeight;
|
||||
|
||||
for (fl::u16 dx = 0; dx < dstWidth; ++dx) {
|
||||
fl::u32 dstX0 = (dx * srcWidth * FP_ONE) / dstWidth;
|
||||
fl::u32 dstX1 = ((dx + 1) * srcWidth * FP_ONE) / dstWidth;
|
||||
|
||||
fl::u64 rSum = 0, gSum = 0, bSum = 0;
|
||||
fl::u32 totalWeight = 0;
|
||||
|
||||
// Find covered source pixels
|
||||
fl::u16 srcY_start = dstY0 / FP_ONE;
|
||||
fl::u16 srcY_end = (dstY1 + FP_ONE - 1) / FP_ONE; // ceil
|
||||
|
||||
fl::u16 srcX_start = dstX0 / FP_ONE;
|
||||
fl::u16 srcX_end = (dstX1 + FP_ONE - 1) / FP_ONE; // ceil
|
||||
|
||||
for (fl::u16 sy = srcY_start; sy < srcY_end; ++sy) {
|
||||
// Calculate vertical overlap in Q8.8
|
||||
fl::u32 sy0 = sy * FP_ONE;
|
||||
fl::u32 sy1 = (sy + 1) * FP_ONE;
|
||||
fl::u32 y_overlap = MIN(dstY1, sy1) - MAX(dstY0, sy0);
|
||||
if (y_overlap == 0)
|
||||
continue;
|
||||
|
||||
for (fl::u16 sx = srcX_start; sx < srcX_end; ++sx) {
|
||||
fl::u32 sx0 = sx * FP_ONE;
|
||||
fl::u32 sx1 = (sx + 1) * FP_ONE;
|
||||
fl::u32 x_overlap = MIN(dstX1, sx1) - MAX(dstX0, sx0);
|
||||
if (x_overlap == 0)
|
||||
continue;
|
||||
|
||||
fl::u32 weight = (x_overlap * y_overlap + (FP_ONE >> 1)) >>
|
||||
8; // Q8.8 * Q8.8 → Q16.16 → Q8.8
|
||||
|
||||
const CRGB &p = src[srcXY.mapToIndex(sx, sy)];
|
||||
rSum += p.r * weight;
|
||||
gSum += p.g * weight;
|
||||
bSum += p.b * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Final division, rounding
|
||||
fl::u8 r =
|
||||
totalWeight ? (rSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
fl::u8 g =
|
||||
totalWeight ? (gSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
fl::u8 b =
|
||||
totalWeight ? (bSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
|
||||
dst[dstXY.mapToIndex(dx, dy)] = CRGB(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
fl::u16 srcWidth = srcXY.getWidth();
|
||||
fl::u16 srcHeight = srcXY.getHeight();
|
||||
fl::u16 dstWidth = dstXY.getWidth();
|
||||
fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
FASTLED_ASSERT(dstWidth <= srcWidth,
|
||||
"Destination width must be <= source width");
|
||||
FASTLED_ASSERT(dstHeight <= srcHeight,
|
||||
"Destination height must be <= source height");
|
||||
const bool destination_is_half_of_source =
|
||||
(dstWidth * 2 == srcWidth) && (dstHeight * 2 == srcHeight);
|
||||
// Attempt to use the downscaleHalf function if the destination is half the
|
||||
// size of the source.
|
||||
if (destination_is_half_of_source) {
|
||||
const bool both_rectangles = (srcXY.getType() == XYMap::kLineByLine) &&
|
||||
(dstXY.getType() == XYMap::kLineByLine);
|
||||
if (both_rectangles) {
|
||||
// If both source and destination are rectangular, we can use the
|
||||
// optimized version
|
||||
downscaleHalf(src, srcWidth, srcHeight, dst);
|
||||
} else {
|
||||
// Otherwise, we need to use the mapped version
|
||||
downscaleHalf(src, srcXY, dst, dstXY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
downscaleArbitrary(src, srcXY, dst, dstXY);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
33
libraries/FastLED/src/fl/downscale.h
Normal file
33
libraries/FastLED/src/fl/downscale.h
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Experimental bilinearn downscaling algorithm. Not tested yet and completely
|
||||
"vibe-coded" by ai.
|
||||
|
||||
If you use this and find an issue then please report it.
|
||||
*/
|
||||
|
||||
#include "fl/int.h"
|
||||
#include "crgb.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class XYMap;
|
||||
|
||||
void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
|
||||
// Optimized versions for downscaling by 50%. This is here for testing purposes
|
||||
// mostly. You should prefer to use downscale(...) instead of calling these
|
||||
// functions. It's important to note that downscale(...) will invoke
|
||||
// downscaleHalf(...) automatically when the source and destination are half the
|
||||
// size of each other.
|
||||
void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight,
|
||||
CRGB *dst);
|
||||
void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
|
||||
} // namespace fl
|
||||
7
libraries/FastLED/src/fl/draw_mode.h
Normal file
7
libraries/FastLED/src/fl/draw_mode.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace fl {
|
||||
|
||||
enum DrawMode { DRAW_MODE_OVERWRITE, DRAW_MODE_BLEND_BY_MAX_BRIGHTNESS };
|
||||
|
||||
} // namespace fl
|
||||
77
libraries/FastLED/src/fl/draw_visitor.h
Normal file
77
libraries/FastLED/src/fl/draw_visitor.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/gradient.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fl/move.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Draws a fl::u8 value to a CRGB array, blending it with the existing color.
|
||||
struct XYDrawComposited {
|
||||
XYDrawComposited(const CRGB &color, const XYMap &xymap, CRGB *out);
|
||||
|
||||
// Copy constructor (assignment deleted due to const members)
|
||||
XYDrawComposited(const XYDrawComposited &other) = default;
|
||||
XYDrawComposited &operator=(const XYDrawComposited &other) = delete;
|
||||
|
||||
// Move constructor (assignment deleted due to const members)
|
||||
XYDrawComposited(XYDrawComposited &&other) noexcept
|
||||
: mColor(fl::move(other.mColor)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {}
|
||||
XYDrawComposited &operator=(XYDrawComposited &&other) noexcept = delete;
|
||||
|
||||
void draw(const vec2<fl::u16> &pt, fl::u32 index, fl::u8 value);
|
||||
const CRGB mColor;
|
||||
const XYMap mXYMap;
|
||||
CRGB *mOut;
|
||||
};
|
||||
|
||||
struct XYDrawGradient {
|
||||
XYDrawGradient(const Gradient &gradient, const XYMap &xymap, CRGB *out);
|
||||
|
||||
// Copy constructor (assignment deleted due to const members)
|
||||
XYDrawGradient(const XYDrawGradient &other) = default;
|
||||
XYDrawGradient &operator=(const XYDrawGradient &other) = delete;
|
||||
|
||||
// Move constructor (assignment deleted due to const members)
|
||||
XYDrawGradient(XYDrawGradient &&other) noexcept
|
||||
: mGradient(fl::move(other.mGradient)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {}
|
||||
XYDrawGradient &operator=(XYDrawGradient &&other) noexcept = delete;
|
||||
|
||||
void draw(const vec2<fl::u16> &pt, fl::u32 index, fl::u8 value);
|
||||
const Gradient mGradient;
|
||||
const XYMap mXYMap;
|
||||
CRGB *mOut;
|
||||
};
|
||||
|
||||
inline XYDrawComposited::XYDrawComposited(const CRGB &color, const XYMap &xymap,
|
||||
CRGB *out)
|
||||
: mColor(color), mXYMap(xymap), mOut(out) {}
|
||||
|
||||
inline void XYDrawComposited::draw(const vec2<fl::u16> &pt, fl::u32 index,
|
||||
fl::u8 value) {
|
||||
FASTLED_UNUSED(pt);
|
||||
CRGB &c = mOut[index];
|
||||
CRGB blended = mColor;
|
||||
blended.fadeToBlackBy(255 - value);
|
||||
c = CRGB::blendAlphaMaxChannel(blended, c);
|
||||
}
|
||||
|
||||
inline XYDrawGradient::XYDrawGradient(const Gradient &gradient,
|
||||
const XYMap &xymap, CRGB *out)
|
||||
: mGradient(gradient), mXYMap(xymap), mOut(out) {}
|
||||
|
||||
inline void XYDrawGradient::draw(const vec2<fl::u16> &pt, fl::u32 index,
|
||||
fl::u8 value) {
|
||||
FASTLED_UNUSED(pt);
|
||||
CRGB c = mGradient.colorAt(value);
|
||||
mOut[index] = c;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
339
libraries/FastLED/src/fl/ease.cpp
Normal file
339
libraries/FastLED/src/fl/ease.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#ifndef FASTLED_INTERNAL
|
||||
#define FASTLED_INTERNAL
|
||||
#endif
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "fl/ease.h"
|
||||
#include "lib8tion.h" // This is the problematic header that's hard to include
|
||||
|
||||
#include "fl/map_range.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/sin32.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction
|
||||
// This table converts linear 8-bit values to gamma-corrected 16-bit values
|
||||
// using a gamma curve of 2.8 (commonly used for LED brightness correction)
|
||||
const u16 gamma_2_8[256] FL_PROGMEM = {
|
||||
0, 0, 0, 1, 1, 2, 4, 6, 8, 11,
|
||||
14, 18, 23, 29, 35, 41, 49, 57, 67, 77,
|
||||
88, 99, 112, 126, 141, 156, 173, 191, 210, 230,
|
||||
251, 274, 297, 322, 348, 375, 404, 433, 464, 497,
|
||||
531, 566, 602, 640, 680, 721, 763, 807, 853, 899,
|
||||
948, 998, 1050, 1103, 1158, 1215, 1273, 1333, 1394, 1458,
|
||||
1523, 1590, 1658, 1729, 1801, 1875, 1951, 2029, 2109, 2190,
|
||||
2274, 2359, 2446, 2536, 2627, 2720, 2816, 2913, 3012, 3114,
|
||||
3217, 3323, 3431, 3541, 3653, 3767, 3883, 4001, 4122, 4245,
|
||||
4370, 4498, 4627, 4759, 4893, 5030, 5169, 5310, 5453, 5599,
|
||||
5747, 5898, 6051, 6206, 6364, 6525, 6688, 6853, 7021, 7191,
|
||||
7364, 7539, 7717, 7897, 8080, 8266, 8454, 8645, 8838, 9034,
|
||||
9233, 9434, 9638, 9845, 10055, 10267, 10482, 10699, 10920, 11143,
|
||||
11369, 11598, 11829, 12064, 12301, 12541, 12784, 13030, 13279, 13530,
|
||||
13785, 14042, 14303, 14566, 14832, 15102, 15374, 15649, 15928, 16209,
|
||||
16493, 16781, 17071, 17365, 17661, 17961, 18264, 18570, 18879, 19191,
|
||||
19507, 19825, 20147, 20472, 20800, 21131, 21466, 21804, 22145, 22489,
|
||||
22837, 23188, 23542, 23899, 24260, 24625, 24992, 25363, 25737, 26115,
|
||||
26496, 26880, 27268, 27659, 28054, 28452, 28854, 29259, 29667, 30079,
|
||||
30495, 30914, 31337, 31763, 32192, 32626, 33062, 33503, 33947, 34394,
|
||||
34846, 35300, 35759, 36221, 36687, 37156, 37629, 38106, 38586, 39071,
|
||||
39558, 40050, 40545, 41045, 41547, 42054, 42565, 43079, 43597, 44119,
|
||||
44644, 45174, 45707, 46245, 46786, 47331, 47880, 48432, 48989, 49550,
|
||||
50114, 50683, 51255, 51832, 52412, 52996, 53585, 54177, 54773, 55374,
|
||||
55978, 56587, 57199, 57816, 58436, 59061, 59690, 60323, 60960, 61601,
|
||||
62246, 62896, 63549, 64207, 64869, 65535};
|
||||
|
||||
// 8-bit easing functions
|
||||
u8 easeInQuad8(u8 i) {
|
||||
// Simple quadratic ease-in: i^2 scaled to 8-bit range
|
||||
// Using scale8(i, i) which computes (i * i) / 255
|
||||
return scale8(i, i);
|
||||
}
|
||||
|
||||
u8 easeInOutQuad8(u8 i) {
|
||||
constexpr u16 MAX = 0xFF; // 255
|
||||
constexpr u16 HALF = (MAX + 1) >> 1; // 128
|
||||
constexpr u16 DENOM = MAX; // divisor for scaling
|
||||
constexpr u16 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (i < HALF) {
|
||||
// first half: y = 2·(i/MAX)² → y_i = 2·i² / MAX
|
||||
u32 t = i;
|
||||
u32 num = 2 * t * t + ROUND; // 2*i², +half for rounding
|
||||
return u8(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − 2·(1−i/MAX)²
|
||||
// → y_i = MAX − (2·(MAX−i)² / MAX)
|
||||
u32 d = MAX - i;
|
||||
u32 num = 2 * d * d + ROUND; // 2*(MAX−i)², +half for rounding
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u8 easeInOutCubic8(u8 i) {
|
||||
constexpr u16 MAX = 0xFF; // 255
|
||||
constexpr u16 HALF = (MAX + 1) >> 1; // 128
|
||||
constexpr u32 DENOM = (u32)MAX * MAX; // 255*255 = 65025
|
||||
constexpr u32 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (i < HALF) {
|
||||
// first half: y = 4·(i/MAX)³ → y_i = 4·i³ / MAX²
|
||||
u32 ii = i;
|
||||
u32 cube = ii * ii * ii; // i³
|
||||
u32 num = 4 * cube + ROUND; // 4·i³, +half denom for rounding
|
||||
return u8(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − ((−2·t+2)³)/2
|
||||
// where t = i/MAX; equivalently:
|
||||
// y_i = MAX − (4·(MAX−i)³ / MAX²)
|
||||
u32 d = MAX - i;
|
||||
u32 cube = d * d * d; // (MAX−i)³
|
||||
u32 num = 4 * cube + ROUND;
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u8 easeOutQuad8(u8 i) {
|
||||
// ease-out is the inverse of ease-in: 1 - (1-t)²
|
||||
// For 8-bit: y = MAX - (MAX-i)² / MAX
|
||||
constexpr u16 MAX = 0xFF;
|
||||
u32 d = MAX - i; // (MAX - i)
|
||||
u32 num = d * d + (MAX >> 1); // (MAX-i)² + rounding
|
||||
return u8(MAX - (num / MAX));
|
||||
}
|
||||
|
||||
u8 easeInCubic8(u8 i) {
|
||||
// Simple cubic ease-in: i³ scaled to 8-bit range
|
||||
// y = i³ / MAX²
|
||||
constexpr u16 MAX = 0xFF;
|
||||
constexpr u32 DENOM = (u32)MAX * MAX;
|
||||
constexpr u32 ROUND = DENOM >> 1;
|
||||
|
||||
u32 ii = i;
|
||||
u32 cube = ii * ii * ii; // i³
|
||||
u32 num = cube + ROUND;
|
||||
return u8(num / DENOM);
|
||||
}
|
||||
|
||||
u8 easeOutCubic8(u8 i) {
|
||||
// ease-out cubic: 1 - (1-t)³
|
||||
// For 8-bit: y = MAX - (MAX-i)³ / MAX²
|
||||
constexpr u16 MAX = 0xFF;
|
||||
constexpr u32 DENOM = (u32)MAX * MAX;
|
||||
constexpr u32 ROUND = DENOM >> 1;
|
||||
|
||||
u32 d = MAX - i; // (MAX - i)
|
||||
u32 cube = d * d * d; // (MAX-i)³
|
||||
u32 num = cube + ROUND;
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
|
||||
u8 easeInSine8(u8 i) {
|
||||
|
||||
static const u8 easeInSineTable[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4,
|
||||
4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8,
|
||||
8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14,
|
||||
15, 16, 16, 17, 17, 18, 18, 19, 20, 20, 21, 21, 22, 23,
|
||||
23, 24, 25, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33,
|
||||
33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 52, 53, 54, 55, 56, 57,
|
||||
58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72,
|
||||
73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84, 86, 87, 88,
|
||||
89, 90, 91, 93, 94, 95, 96, 98, 99, 100, 101, 103, 104, 105,
|
||||
106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 121, 122, 123,
|
||||
125, 126, 127, 129, 130, 132, 133, 134, 136, 137, 139, 140, 141, 143,
|
||||
144, 146, 147, 148, 150, 151, 153, 154, 156, 157, 159, 160, 161, 163,
|
||||
164, 166, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181, 182, 184,
|
||||
185, 187, 188, 190, 191, 193, 194, 196, 197, 199, 200, 202, 204, 205,
|
||||
207, 208, 210, 211, 213, 214, 216, 217, 219, 221, 222, 224, 225, 227,
|
||||
228, 230, 231, 233, 235, 236, 238, 239, 241, 242, 244, 246, 247, 249,
|
||||
250, 252, 253, 255};
|
||||
|
||||
// ease-in sine: 1 - cos(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
return easeInSineTable[i];
|
||||
}
|
||||
|
||||
u8 easeOutSine8(u8 i) {
|
||||
// ease-out sine: sin(t * π/2)
|
||||
// Delegate to 16-bit version for consistency and accuracy
|
||||
// Scale 8-bit input to 16-bit range, call 16-bit function, scale result back
|
||||
u16 input16 = map8_to_16(i);
|
||||
u16 result16 = easeOutSine16(input16);
|
||||
return map16_to_8(result16);
|
||||
}
|
||||
|
||||
u8 easeInOutSine8(u8 i) {
|
||||
// ease-in-out sine: -(cos(π*t) - 1) / 2
|
||||
// Delegate to 16-bit version for consistency and accuracy
|
||||
// Scale 8-bit input to 16-bit range, call 16-bit function, scale result back
|
||||
u16 input16 = map8_to_16(i);
|
||||
u16 result16 = easeInOutSine16(input16);
|
||||
return map16_to_8(result16);
|
||||
}
|
||||
|
||||
// 16-bit easing functions
|
||||
u16 easeInQuad16(u16 i) {
|
||||
// Simple quadratic ease-in: i^2 scaled to 16-bit range
|
||||
// Using scale16(i, i) which computes (i * i) / 65535
|
||||
return scale16(i, i);
|
||||
}
|
||||
|
||||
u16 easeInOutQuad16(u16 x) {
|
||||
// 16-bit quadratic ease-in / ease-out function
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr u32 HALF = (MAX + 1) >> 1; // 32768
|
||||
constexpr u32 DENOM = MAX; // divisor
|
||||
constexpr u32 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (x < HALF) {
|
||||
// first half: y = 2·(x/MAX)² → y_i = 2·x² / MAX
|
||||
fl::u64 xi = x;
|
||||
fl::u64 num = 2 * xi * xi + ROUND; // 2*x², +half for rounding
|
||||
return u16(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − 2·(1−x/MAX)² → y_i = MAX − (2·(MAX−x)² / MAX)
|
||||
fl::u64 d = MAX - x;
|
||||
fl::u64 num = 2 * d * d + ROUND; // 2*(MAX−x)², +half for rounding
|
||||
return u16(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u16 easeInOutCubic16(u16 x) {
|
||||
const u32 MAX = 0xFFFF; // 65535
|
||||
const u32 HALF = (MAX + 1) >> 1; // 32768
|
||||
const fl::u64 M2 = (fl::u64)MAX * MAX; // 65535² = 4 294 836 225
|
||||
|
||||
if (x < HALF) {
|
||||
// first half: y = 4·(x/MAX)³ → y_i = 4·x³ / MAX²
|
||||
fl::u64 xi = x;
|
||||
fl::u64 cube = xi * xi * xi; // x³
|
||||
// add M2/2 for rounding
|
||||
fl::u64 num = 4 * cube + (M2 >> 1);
|
||||
return (u16)(num / M2);
|
||||
} else {
|
||||
// second half: y = 1 − ((2·(1−x/MAX))³)/2
|
||||
// → y_i = MAX − (4·(MAX−x)³ / MAX²)
|
||||
fl::u64 d = MAX - x;
|
||||
fl::u64 cube = d * d * d; // (MAX−x)³
|
||||
fl::u64 num = 4 * cube + (M2 >> 1);
|
||||
return (u16)(MAX - (num / M2));
|
||||
}
|
||||
}
|
||||
|
||||
u16 easeOutQuad16(u16 i) {
|
||||
// ease-out quadratic: 1 - (1-t)²
|
||||
// For 16-bit: y = MAX - (MAX-i)² / MAX
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr u32 ROUND = MAX >> 1; // for rounding
|
||||
|
||||
fl::u64 d = MAX - i; // (MAX - i)
|
||||
fl::u64 num = d * d + ROUND; // (MAX-i)² + rounding
|
||||
return u16(MAX - (num / MAX));
|
||||
}
|
||||
|
||||
u16 easeInCubic16(u16 i) {
|
||||
// Simple cubic ease-in: i³ scaled to 16-bit range
|
||||
// y = i³ / MAX²
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535²
|
||||
constexpr fl::u64 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
fl::u64 ii = i;
|
||||
fl::u64 cube = ii * ii * ii; // i³
|
||||
fl::u64 num = cube + ROUND;
|
||||
return u16(num / DENOM);
|
||||
}
|
||||
|
||||
u16 easeOutCubic16(u16 i) {
|
||||
// ease-out cubic: 1 - (1-t)³
|
||||
// For 16-bit: y = MAX - (MAX-i)³ / MAX²
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535²
|
||||
constexpr fl::u64 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
fl::u64 d = MAX - i; // (MAX - i)
|
||||
fl::u64 cube = d * d * d; // (MAX-i)³
|
||||
fl::u64 num = cube + ROUND;
|
||||
return u16(MAX - (num / DENOM));
|
||||
}
|
||||
|
||||
u16 easeInSine16(u16 i) {
|
||||
// ease-in sine: 1 - cos(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
// Remove the hard-coded boundary for 65535 and let math handle it
|
||||
|
||||
// For 16-bit: use cos32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,4194304] in cos32 space (zero to quarter wave)
|
||||
// Formula: 1 - cos(t * π/2) where t goes from 0 to 1
|
||||
// sin32/cos32 quarter cycle is 16777216/4 = 4194304
|
||||
u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL;
|
||||
i32 cos_result = fl::cos32(angle);
|
||||
|
||||
// Convert cos32 output and apply easing formula: 1 - cos(t * π/2)
|
||||
// cos32 output range is [-2147418112, 2147418112]
|
||||
// At t=0: cos(0) = 2147418112, result should be 0
|
||||
// At t=1: cos(π/2) = 0, result should be 65535
|
||||
|
||||
const fl::i64 MAX_COS32 = 2147418112LL;
|
||||
|
||||
// Calculate: (MAX_COS32 - cos_result) and scale to [0, 65535]
|
||||
fl::i64 adjusted = MAX_COS32 - (fl::i64)cos_result;
|
||||
|
||||
// Scale from [0, 2147418112] to [0, 65535]
|
||||
fl::u64 result = (fl::u64)adjusted * 65535ULL + (MAX_COS32 >> 1); // Add half for rounding
|
||||
u16 final_result = (u16)(result / (fl::u64)MAX_COS32);
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
u16 easeOutSine16(u16 i) {
|
||||
// ease-out sine: sin(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
if (i == 65535)
|
||||
return 65535;
|
||||
|
||||
// For 16-bit: use sin32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,4194304] in sin32 space (zero to quarter wave)
|
||||
// Formula: sin(t * π/2) where t goes from 0 to 1
|
||||
// sin32 quarter cycle is 16777216/4 = 4194304
|
||||
u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL;
|
||||
i32 sin_result = fl::sin32(angle);
|
||||
|
||||
// Convert sin32 output range [-2147418112, 2147418112] to [0, 65535]
|
||||
// sin32 output is in range -32767*65536 to +32767*65536
|
||||
// For ease-out sine, we only use positive portion [0, 2147418112] -> [0, 65535]
|
||||
return (u16)((fl::u64)sin_result * 65535ULL / 2147418112ULL);
|
||||
}
|
||||
|
||||
u16 easeInOutSine16(u16 i) {
|
||||
// ease-in-out sine: -(cos(π*t) - 1) / 2
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
if (i == 65535)
|
||||
return 65535;
|
||||
|
||||
// For 16-bit: use cos32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,8388608] in cos32 space (0 to half wave)
|
||||
// Formula: (1 - cos(π*t)) / 2 where t goes from 0 to 1
|
||||
// sin32/cos32 half cycle is 16777216/2 = 8388608
|
||||
u32 angle = ((fl::u64)i * 8388608ULL) / 65535ULL;
|
||||
i32 cos_result = fl::cos32(angle);
|
||||
|
||||
// Convert cos32 output and apply easing formula: (1 - cos(π*t)) / 2
|
||||
// cos32 output range is [-2147418112, 2147418112]
|
||||
// We want: (2147418112 - cos_result) / 2, then scale to [0, 65535]
|
||||
fl::i64 adjusted = (2147418112LL - (fl::i64)cos_result) / 2;
|
||||
return (u16)((fl::u64)adjusted * 65535ULL / 2147418112ULL);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
278
libraries/FastLED/src/fl/ease.h
Normal file
278
libraries/FastLED/src/fl/ease.h
Normal file
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
This are accurate and tested easing functions.
|
||||
|
||||
Note that the easing functions in lib8tion.h are tuned are implemented wrong, such as easeInOutCubic8 and easeInOutCubic16.
|
||||
Modern platforms are so fast that the extra performance is not needed, but accuracy is important.
|
||||
|
||||
*/
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fastled_progmem.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction
|
||||
// Used for converting linear 8-bit values to gamma-corrected 16-bit values
|
||||
extern const u16 gamma_2_8[256] FL_PROGMEM;
|
||||
|
||||
enum EaseType {
|
||||
EASE_NONE,
|
||||
EASE_IN_QUAD,
|
||||
EASE_OUT_QUAD,
|
||||
EASE_IN_OUT_QUAD,
|
||||
EASE_IN_CUBIC,
|
||||
EASE_OUT_CUBIC,
|
||||
EASE_IN_OUT_CUBIC,
|
||||
EASE_IN_SINE,
|
||||
EASE_OUT_SINE,
|
||||
EASE_IN_OUT_SINE,
|
||||
};
|
||||
|
||||
// 8-bit easing functions
|
||||
/// 8-bit quadratic ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts slow and accelerates (ease-in only)
|
||||
u8 easeInQuad8(u8 i);
|
||||
|
||||
/// 8-bit quadratic ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts fast and decelerates (ease-out only)
|
||||
u8 easeOutQuad8(u8 i);
|
||||
|
||||
/// 8-bit quadratic ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts slow, accelerates in the middle, then slows down again
|
||||
u8 easeInOutQuad8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced acceleration than quadratic
|
||||
u8 easeInCubic8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced deceleration than quadratic
|
||||
u8 easeOutCubic8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced easing curve than quadratic
|
||||
u8 easeInOutCubic8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal acceleration
|
||||
u8 easeInSine8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal deceleration
|
||||
u8 easeOutSine8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal acceleration and deceleration
|
||||
u8 easeInOutSine8(u8 i);
|
||||
|
||||
|
||||
// 16-bit easing functions
|
||||
/// 16-bit quadratic ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInQuad16(u16 i);
|
||||
|
||||
/// 16-bit quadratic ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutQuad16(u16 i);
|
||||
|
||||
/// 16-bit quadratic ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutQuad16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInCubic16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutCubic16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutCubic16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInSine16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutSine16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutSine16(u16 i);
|
||||
|
||||
u16 ease16(EaseType type, u16 i);
|
||||
void ease16(EaseType type, u16* src, u16* dst, u16 count);
|
||||
u8 ease8(EaseType type, u8 i);
|
||||
void ease8(EaseType type, u8* src, u8* dst, u8 count);
|
||||
|
||||
|
||||
//////// INLINE FUNCTIONS ////////
|
||||
|
||||
inline u16 ease16(EaseType type, u16 i) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return i;
|
||||
case EASE_IN_QUAD: return easeInQuad16(i);
|
||||
case EASE_OUT_QUAD: return easeOutQuad16(i);
|
||||
case EASE_IN_OUT_QUAD: return easeInOutQuad16(i);
|
||||
case EASE_IN_CUBIC: return easeInCubic16(i);
|
||||
case EASE_OUT_CUBIC: return easeOutCubic16(i);
|
||||
case EASE_IN_OUT_CUBIC: return easeInOutCubic16(i);
|
||||
case EASE_IN_SINE: return easeInSine16(i);
|
||||
case EASE_OUT_SINE: return easeOutSine16(i);
|
||||
case EASE_IN_OUT_SINE: return easeInOutSine16(i);
|
||||
default: return i;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ease16(EaseType type, u16* src, u16* dst, u16 count) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return;
|
||||
case EASE_IN_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline u8 ease8(EaseType type, u8 i) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return i;
|
||||
case EASE_IN_QUAD: return easeInQuad8(i);
|
||||
case EASE_OUT_QUAD: return easeOutQuad8(i);
|
||||
case EASE_IN_OUT_QUAD: return easeInOutQuad8(i);
|
||||
case EASE_IN_CUBIC: return easeInCubic8(i);
|
||||
case EASE_OUT_CUBIC: return easeOutCubic8(i);
|
||||
case EASE_IN_OUT_CUBIC: return easeInOutCubic8(i);
|
||||
case EASE_IN_SINE: return easeInSine8(i);
|
||||
case EASE_OUT_SINE: return easeOutSine8(i);
|
||||
case EASE_IN_OUT_SINE: return easeInOutSine8(i);
|
||||
default: return i;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ease8(EaseType type, u8* src, u8* dst, u8 count) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return;
|
||||
case EASE_IN_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
131
libraries/FastLED/src/fl/engine_events.cpp
Normal file
131
libraries/FastLED/src/fl/engine_events.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
|
||||
EngineEvents::Listener::Listener() {}
|
||||
|
||||
EngineEvents::Listener::~Listener() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents *ptr = EngineEvents::getInstance();
|
||||
const bool has_listener = ptr && ptr->_hasListener(this);
|
||||
if (has_listener) {
|
||||
// Warning, the listener should be removed by the subclass. If we are
|
||||
// here then the subclass did not remove the listener and we are now in
|
||||
// a partial state of destruction and the results may be undefined for
|
||||
// multithreaded applications. However, for single threaded (the only
|
||||
// option as of 2024, October) this will be ok.
|
||||
ptr->_removeListener(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents *EngineEvents::getInstance() {
|
||||
return &Singleton<EngineEvents>::instance();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
void EngineEvents::_onPlatformPreLoop() {
|
||||
for (auto &item : mListeners) {
|
||||
auto listener = item.listener;
|
||||
listener->onPlatformPreLoop();
|
||||
}
|
||||
for (auto &item : mListeners) {
|
||||
auto listener = item.listener;
|
||||
listener->onPlatformPreLoop2();
|
||||
}
|
||||
}
|
||||
|
||||
bool EngineEvents::_hasListener(Listener *listener) {
|
||||
auto predicate = [listener](const Pair &pair) {
|
||||
return pair.listener == listener;
|
||||
};
|
||||
return mListeners.find_if(predicate) != mListeners.end();
|
||||
}
|
||||
|
||||
void EngineEvents::_addListener(Listener *listener, int priority) {
|
||||
if (_hasListener(listener)) {
|
||||
return;
|
||||
}
|
||||
for (auto it = mListeners.begin(); it != mListeners.end(); ++it) {
|
||||
if (it->priority < priority) {
|
||||
// this is now the highest priority in this spot.
|
||||
EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
|
||||
mListeners.insert(it, pair);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
|
||||
mListeners.push_back(pair);
|
||||
}
|
||||
|
||||
void EngineEvents::_removeListener(Listener *listener) {
|
||||
auto predicate = [listener](const Pair &pair) {
|
||||
return pair.listener == listener;
|
||||
};
|
||||
auto it = mListeners.find_if(predicate);
|
||||
if (it != mListeners.end()) {
|
||||
mListeners.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onBeginFrame() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onBeginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onEndShowLeds() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onEndShowLeds();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onEndFrame() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onEndFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onStripAdded(strip, num_leds);
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onCanvasUiSet(CLEDController *strip,
|
||||
const ScreenMap &screenmap) {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onCanvasUiSet(strip, screenmap);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
|
||||
} // namespace fl
|
||||
150
libraries/FastLED/src/fl/engine_events.h
Normal file
150
libraries/FastLED/src/fl/engine_events.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#ifndef FASTLED_HAS_ENGINE_EVENTS
|
||||
#define FASTLED_HAS_ENGINE_EVENTS SKETCH_HAS_LOTS_OF_MEMORY
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
class CLEDController;
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
class EngineEvents {
|
||||
public:
|
||||
class Listener {
|
||||
public:
|
||||
// Note that the subclass must call EngineEvents::addListener(this) to
|
||||
// start listening. In the subclass destructor, the subclass should call
|
||||
// EngineEvents::removeListener(this).
|
||||
Listener();
|
||||
virtual ~Listener();
|
||||
virtual void onBeginFrame() {}
|
||||
virtual void onEndShowLeds() {}
|
||||
virtual void onEndFrame() {}
|
||||
virtual void onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
(void)strip;
|
||||
(void)num_leds;
|
||||
}
|
||||
// Called to set the canvas for UI elements for a particular strip.
|
||||
virtual void onCanvasUiSet(CLEDController *strip,
|
||||
const ScreenMap &screenmap) {
|
||||
(void)strip;
|
||||
(void)screenmap;
|
||||
}
|
||||
virtual void onPlatformPreLoop() {}
|
||||
virtual void onPlatformPreLoop2() {}
|
||||
};
|
||||
|
||||
static void addListener(Listener *listener, int priority = 0) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_addListener(listener, priority);
|
||||
#else
|
||||
(void)listener;
|
||||
(void)priority;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void removeListener(Listener *listener) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_removeListener(listener);
|
||||
#else
|
||||
(void)listener;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool hasListener(Listener *listener) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
return EngineEvents::getInstance()->_hasListener(listener);
|
||||
#else
|
||||
(void)listener;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onBeginFrame() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onBeginFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onEndShowLeds() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onEndShowLeds();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onEndFrame() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onEndFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onStripAdded(strip, num_leds);
|
||||
#else
|
||||
(void)strip;
|
||||
(void)num_leds;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onCanvasUiSet(strip, xymap);
|
||||
#else
|
||||
(void)strip;
|
||||
(void)xymap;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onPlatformPreLoop() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onPlatformPreLoop();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Needed by fl::vector<T>
|
||||
EngineEvents() = default;
|
||||
|
||||
private:
|
||||
// Safe to add a listeners during a callback.
|
||||
void _addListener(Listener *listener, int priority);
|
||||
// Safe to remove self during a callback.
|
||||
void _removeListener(Listener *listener);
|
||||
void _onBeginFrame();
|
||||
void _onEndShowLeds();
|
||||
void _onEndFrame();
|
||||
void _onStripAdded(CLEDController *strip, fl::u32 num_leds);
|
||||
void _onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap);
|
||||
void _onPlatformPreLoop();
|
||||
bool _hasListener(Listener *listener);
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
struct Pair {
|
||||
Pair() = default;
|
||||
Listener *listener = nullptr;
|
||||
int priority = 0;
|
||||
Pair(Listener *listener, int priority)
|
||||
: listener(listener), priority(priority) {}
|
||||
};
|
||||
|
||||
typedef fl::vector_inlined<Pair, 16> ListenerList;
|
||||
ListenerList mListeners;
|
||||
|
||||
|
||||
static EngineEvents *getInstance();
|
||||
|
||||
friend class fl::Singleton<EngineEvents>;
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/eorder.h
Normal file
31
libraries/FastLED/src/fl/eorder.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/// @file fl/eorder.h
|
||||
/// Defines color channel ordering enumerations in the fl namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// RGB color channel orderings, used when instantiating controllers to determine
|
||||
/// what order the controller should send data out in. The default ordering
|
||||
/// is RGB.
|
||||
/// Within this enum, the red channel is 0, the green channel is 1, and the
|
||||
/// blue chanel is 2.
|
||||
enum EOrder {
|
||||
RGB=0012, ///< Red, Green, Blue (0012)
|
||||
RBG=0021, ///< Red, Blue, Green (0021)
|
||||
GRB=0102, ///< Green, Red, Blue (0102)
|
||||
GBR=0120, ///< Green, Blue, Red (0120)
|
||||
BRG=0201, ///< Blue, Red, Green (0201)
|
||||
BGR=0210 ///< Blue, Green, Red (0210)
|
||||
};
|
||||
|
||||
// After EOrder is applied this is where W is inserted for RGBW.
|
||||
enum EOrderW {
|
||||
W3 = 0x3, ///< White is fourth
|
||||
W2 = 0x2, ///< White is third
|
||||
W1 = 0x1, ///< White is second
|
||||
W0 = 0x0, ///< White is first
|
||||
WDefault = W3
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
40
libraries/FastLED/src/fl/export.h
Normal file
40
libraries/FastLED/src/fl/export.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
/// @file export.h
|
||||
/// Cross-platform export macros for FastLED dynamic library support
|
||||
|
||||
#ifndef FASTLED_EXPORT
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
#define FASTLED_EXPORT EMSCRIPTEN_KEEPALIVE
|
||||
#elif defined(_WIN32) || defined(_WIN64)
|
||||
// Windows DLL export/import
|
||||
#ifdef FASTLED_BUILDING_DLL
|
||||
#define FASTLED_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define FASTLED_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined(__GNUC__) && (__GNUC__ >= 4)
|
||||
// GCC/Clang visibility attributes
|
||||
#define FASTLED_EXPORT __attribute__((visibility("default")))
|
||||
#elif defined(__SUNPRO_CC) && (__SUNPRO_CC >= 0x550)
|
||||
// Sun Studio visibility attributes
|
||||
#define FASTLED_EXPORT __global
|
||||
#else
|
||||
// Fallback for other platforms
|
||||
#define FASTLED_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef FASTLED_CALL
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// Windows calling convention
|
||||
#define FASTLED_CALL __stdcall
|
||||
#else
|
||||
// Unix-like platforms - no special calling convention
|
||||
#define FASTLED_CALL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// Combined export and calling convention macro
|
||||
#define FASTLED_API FASTLED_EXPORT FASTLED_CALL
|
||||
333
libraries/FastLED/src/fl/fetch.cpp
Normal file
333
libraries/FastLED/src/fl/fetch.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "fl/fetch.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/mutex.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/async.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/val.h>
|
||||
#endif
|
||||
|
||||
// Include WASM-specific implementation
|
||||
#include "platforms/wasm/js_fetch.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// ========== WASM Implementation using JavaScript fetch ==========
|
||||
|
||||
|
||||
|
||||
// Promise storage moved to FetchManager singleton
|
||||
|
||||
// Use existing WASM fetch infrastructure
|
||||
void fetch(const fl::string& url, const FetchCallback& callback) {
|
||||
// Use the existing WASM fetch implementation - no conversion needed since both use fl::response
|
||||
wasm_fetch.get(url).response(callback);
|
||||
}
|
||||
|
||||
// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request) {
|
||||
// Create a promise for this request
|
||||
auto promise = fl::promise<response>::create();
|
||||
|
||||
// Register with fetch manager to ensure it's tracked
|
||||
FetchManager::instance().register_promise(promise);
|
||||
|
||||
// Get the actual URL to use (use request URL if provided, otherwise use parameter URL)
|
||||
fl::string fetch_url = request.url().empty() ? url : request.url();
|
||||
|
||||
// Convert our request to the existing WASM fetch system
|
||||
auto wasm_request = WasmFetchRequest(fetch_url);
|
||||
|
||||
// Use lambda that captures the promise directly (shared_ptr is safe to copy)
|
||||
// Make the lambda mutable so we can call non-const methods on the captured promise
|
||||
wasm_request.response([promise](const response& resp) mutable {
|
||||
// Complete the promise directly - no need for double storage
|
||||
if (promise.valid()) {
|
||||
promise.complete_with_value(resp);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else
|
||||
// ========== Embedded/Stub Implementation ==========
|
||||
|
||||
void fetch(const fl::string& url, const FetchCallback& callback) {
|
||||
(void)url; // Unused in stub implementation
|
||||
// For embedded platforms, immediately call callback with a "not supported" response
|
||||
response resp(501, "Not Implemented");
|
||||
resp.set_text("HTTP fetch not supported on this platform");
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request) {
|
||||
(void)request; // Unused in stub implementation
|
||||
FL_WARN("HTTP fetch is not supported on non-WASM platforms. URL: " << url);
|
||||
|
||||
// Create error response
|
||||
response error_response(501, "Not Implemented");
|
||||
error_response.set_body("HTTP fetch is only available in WASM/browser builds. This platform does not support network requests.");
|
||||
|
||||
// Create resolved promise with error response
|
||||
auto promise = fl::promise<response>::resolve(error_response);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// ========== Engine Events Integration ==========
|
||||
|
||||
|
||||
|
||||
// ========== Promise-Based API Implementation ==========
|
||||
|
||||
class FetchEngineListener : public EngineEvents::Listener {
|
||||
public:
|
||||
FetchEngineListener() {
|
||||
EngineEvents::addListener(this);
|
||||
};
|
||||
~FetchEngineListener() override {
|
||||
// Listener base class automatically removes itself
|
||||
EngineEvents::removeListener(this);
|
||||
}
|
||||
|
||||
void onEndFrame() override {
|
||||
// Update all async tasks (fetch, timers, etc.) at the end of each frame
|
||||
fl::async_run();
|
||||
}
|
||||
};
|
||||
|
||||
FetchManager& FetchManager::instance() {
|
||||
return fl::Singleton<FetchManager>::instance();
|
||||
}
|
||||
|
||||
void FetchManager::register_promise(const fl::promise<response>& promise) {
|
||||
// Auto-register with async system and engine listener on first promise
|
||||
if (mActivePromises.empty()) {
|
||||
AsyncManager::instance().register_runner(this);
|
||||
|
||||
if (!mEngineListener) {
|
||||
mEngineListener = fl::make_unique<FetchEngineListener>();
|
||||
EngineEvents::addListener(mEngineListener.get());
|
||||
}
|
||||
}
|
||||
|
||||
mActivePromises.push_back(promise);
|
||||
}
|
||||
|
||||
void FetchManager::update() {
|
||||
// Update all active promises first
|
||||
for (auto& promise : mActivePromises) {
|
||||
if (promise.valid()) {
|
||||
promise.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Then clean up completed/invalid promises in a separate pass
|
||||
cleanup_completed_promises();
|
||||
|
||||
// Auto-unregister from async system when no more promises
|
||||
if (mActivePromises.empty()) {
|
||||
AsyncManager::instance().unregister_runner(this);
|
||||
|
||||
if (mEngineListener) {
|
||||
EngineEvents::removeListener(mEngineListener.get());
|
||||
mEngineListener.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FetchManager::has_active_tasks() const {
|
||||
return !mActivePromises.empty();
|
||||
}
|
||||
|
||||
size_t FetchManager::active_task_count() const {
|
||||
return mActivePromises.size();
|
||||
}
|
||||
|
||||
fl::size FetchManager::active_requests() const {
|
||||
return mActivePromises.size();
|
||||
}
|
||||
|
||||
void FetchManager::cleanup_completed_promises() {
|
||||
// Rebuild vector without completed promises
|
||||
fl::vector<fl::promise<response>> active_promises;
|
||||
for (const auto& promise : mActivePromises) {
|
||||
if (promise.valid() && !promise.is_completed()) {
|
||||
active_promises.push_back(promise);
|
||||
}
|
||||
}
|
||||
mActivePromises = fl::move(active_promises);
|
||||
}
|
||||
|
||||
// WASM promise management methods removed - no longer needed
|
||||
// Promises are now handled directly via shared_ptr capture in callbacks
|
||||
|
||||
// ========== Public API Functions ==========
|
||||
|
||||
fl::promise<response> fetch_get(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with GET method
|
||||
fetch_options get_request(url, RequestOptions("GET"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
get_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
get_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
get_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, get_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_post(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with POST method
|
||||
fetch_options post_request(url, RequestOptions("POST"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
post_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
post_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
post_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, post_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_put(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with PUT method
|
||||
fetch_options put_request(url, RequestOptions("PUT"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
put_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
put_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
put_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, put_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_delete(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with DELETE method
|
||||
fetch_options delete_request(url, RequestOptions("DELETE"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
delete_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
delete_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
delete_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, delete_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_head(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with HEAD method
|
||||
fetch_options head_request(url, RequestOptions("HEAD"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
head_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
head_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
head_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, head_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_http_options(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with OPTIONS method
|
||||
fetch_options options_request(url, RequestOptions("OPTIONS"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
options_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
options_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
options_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, options_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_patch(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with PATCH method
|
||||
fetch_options patch_request(url, RequestOptions("PATCH"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
patch_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
patch_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
patch_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, patch_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_request(const fl::string& url, const RequestOptions& options) {
|
||||
// Create a fetch_options with the provided options
|
||||
fetch_options request(url, options);
|
||||
|
||||
// Use the helper function to execute the request
|
||||
return execute_fetch_request(url, request);
|
||||
}
|
||||
|
||||
void fetch_update() {
|
||||
// Legacy function - use fl::async_run() for new code
|
||||
// This provides backwards compatibility for existing code
|
||||
fl::async_run();
|
||||
}
|
||||
|
||||
fl::size fetch_active_requests() {
|
||||
return FetchManager::instance().active_requests();
|
||||
}
|
||||
|
||||
// ========== Response Class Method Implementations ==========
|
||||
|
||||
fl::Json response::json() const {
|
||||
if (!mJsonParsed) {
|
||||
if (is_json() || mBody.find("{") != fl::string::npos || mBody.find("[") != fl::string::npos) {
|
||||
mCachedJson = parse_json_body();
|
||||
} else {
|
||||
FL_WARN("Response is not JSON: " << mBody);
|
||||
mCachedJson = fl::Json(nullptr); // Not JSON content
|
||||
}
|
||||
mJsonParsed = true;
|
||||
}
|
||||
|
||||
return mCachedJson.has_value() ? *mCachedJson : fl::Json(nullptr);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
326
libraries/FastLED/src/fl/fetch.h
Normal file
326
libraries/FastLED/src/fl/fetch.h
Normal file
@@ -0,0 +1,326 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fetch.h
|
||||
/// @brief Unified HTTP fetch API for FastLED (cross-platform)
|
||||
///
|
||||
/// This API provides both simple callback-based and JavaScript-like promise-based interfaces
|
||||
/// for HTTP requests. Works on WASM/browser platforms with real fetch, provides stubs on embedded.
|
||||
///
|
||||
/// **WASM Optimization:** On WASM platforms, `delay()` automatically pumps all async tasks
|
||||
/// (fetch, timers, etc.) in 1ms intervals, making delay time useful for processing async operations.
|
||||
///
|
||||
/// @section Simple Callback Usage
|
||||
/// @code
|
||||
/// #include "fl/fetch.h"
|
||||
///
|
||||
/// void setup() {
|
||||
/// // Simple callback-based fetch (backward compatible)
|
||||
/// fl::fetch("http://fastled.io", [](const fl::response& resp) {
|
||||
/// if (resp.ok()) {
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// @section Promise Usage
|
||||
/// @code
|
||||
/// #include "fl/fetch.h"
|
||||
///
|
||||
/// void setup() {
|
||||
/// // JavaScript-like fetch with promises
|
||||
/// fl::fetch_get("http://fastled.io")
|
||||
/// .then([](const fl::response& resp) {
|
||||
/// if (resp.ok()) {
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// } else {
|
||||
/// FL_WARN("HTTP Error: " << resp.status() << " " << resp.status_text());
|
||||
/// }
|
||||
/// })
|
||||
/// .catch_([](const fl::Error& err) {
|
||||
/// FL_WARN("Fetch Error: " << err.message);
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// void loop() {
|
||||
/// // Fetch promises are automatically updated through FastLED's engine events!
|
||||
/// // On WASM platforms, delay() also pumps all async tasks automatically.
|
||||
/// // No manual updates needed - just use normal FastLED loop
|
||||
/// FastLED.show();
|
||||
/// delay(16); // delay() automatically pumps all async tasks on WASM
|
||||
/// }
|
||||
/// @endcode
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/promise.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/map.h"
|
||||
#include "fl/hash_map.h"
|
||||
#include "fl/optional.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/async.h"
|
||||
#include "fl/mutex.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/json.h" // Add JSON support for response.json() method
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class fetch_options;
|
||||
class FetchManager;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Forward declarations for WASM-specific types (defined in platforms/wasm/js_fetch.h)
|
||||
class WasmFetchRequest;
|
||||
class WasmFetch;
|
||||
using FetchResponseCallback = fl::function<void(const response&)>;
|
||||
extern WasmFetch wasm_fetch;
|
||||
#endif
|
||||
|
||||
/// HTTP response class (unified interface)
|
||||
class response {
|
||||
public:
|
||||
response() : mStatusCode(200), mStatusText("OK") {}
|
||||
response(int status_code) : mStatusCode(status_code), mStatusText(get_default_status_text(status_code)) {}
|
||||
response(int status_code, const fl::string& status_text)
|
||||
: mStatusCode(status_code), mStatusText(status_text) {}
|
||||
|
||||
/// HTTP status code (like JavaScript response.status)
|
||||
int status() const { return mStatusCode; }
|
||||
|
||||
/// HTTP status text (like JavaScript response.statusText)
|
||||
const fl::string& status_text() const { return mStatusText; }
|
||||
|
||||
/// Check if response is successful (like JavaScript response.ok)
|
||||
bool ok() const { return mStatusCode >= 200 && mStatusCode < 300; }
|
||||
|
||||
/// Response body as text (like JavaScript response.text())
|
||||
const fl::string& text() const { return mBody; }
|
||||
|
||||
/// Get header value (like JavaScript response.headers.get())
|
||||
fl::optional<fl::string> get_header(const fl::string& name) const {
|
||||
auto it = mHeaders.find(name);
|
||||
if (it != mHeaders.end()) {
|
||||
return fl::make_optional(it->second);
|
||||
}
|
||||
return fl::nullopt;
|
||||
}
|
||||
|
||||
/// Get content type convenience method
|
||||
fl::optional<fl::string> get_content_type() const {
|
||||
return get_header("content-type");
|
||||
}
|
||||
|
||||
/// Response body as text (alternative to text())
|
||||
const fl::string& get_body_text() const { return mBody; }
|
||||
|
||||
/// Response body parsed as JSON (JavaScript-like API)
|
||||
/// @return fl::Json object for safe, ergonomic access
|
||||
/// @note Automatically parses JSON on first call, caches result
|
||||
/// @note Returns null JSON object for non-JSON or malformed content
|
||||
fl::Json json() const;
|
||||
|
||||
/// Check if response appears to contain JSON content
|
||||
/// @return true if Content-Type header indicates JSON or body contains JSON markers
|
||||
bool is_json() const {
|
||||
auto content_type = get_content_type();
|
||||
if (content_type.has_value()) {
|
||||
fl::string ct = *content_type;
|
||||
// Check for various JSON content types (case-insensitive)
|
||||
return ct.find("json") != fl::string::npos;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Set methods (internal use)
|
||||
void set_status(int status_code) { mStatusCode = status_code; }
|
||||
void set_status_text(const fl::string& status_text) { mStatusText = status_text; }
|
||||
void set_text(const fl::string& body) { mBody = body; } // Backward compatibility
|
||||
void set_body(const fl::string& body) { mBody = body; }
|
||||
void set_header(const fl::string& name, const fl::string& value) {
|
||||
mHeaders[name] = value;
|
||||
}
|
||||
|
||||
private:
|
||||
int mStatusCode;
|
||||
fl::string mStatusText;
|
||||
fl::string mBody;
|
||||
fl_map<fl::string, fl::string> mHeaders;
|
||||
|
||||
// JSON parsing cache
|
||||
mutable fl::optional<fl::Json> mCachedJson; // Lazy-loaded JSON cache
|
||||
mutable bool mJsonParsed = false; // Track parsing attempts
|
||||
|
||||
/// Parse JSON from response body with error handling
|
||||
fl::Json parse_json_body() const {
|
||||
fl::Json parsed = fl::Json::parse(mBody);
|
||||
if (parsed.is_null() && (!mBody.empty())) {
|
||||
// If parsing failed but we have content, return null JSON
|
||||
// This allows safe chaining: resp.json()["key"] | default
|
||||
return fl::Json(nullptr);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static fl::string get_default_status_text(int status) {
|
||||
switch (status) {
|
||||
case 200: return "OK";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// Callback type for simple fetch responses (backward compatible)
|
||||
using FetchCallback = fl::function<void(const response&)>;
|
||||
|
||||
/// Request options (matches JavaScript fetch RequestInit)
|
||||
struct RequestOptions {
|
||||
fl::string method = "GET";
|
||||
fl_map<fl::string, fl::string> headers;
|
||||
fl::string body;
|
||||
int timeout_ms = 10000; // 10 second default
|
||||
|
||||
RequestOptions() = default;
|
||||
RequestOptions(const fl::string& method_name) : method(method_name) {}
|
||||
};
|
||||
|
||||
/// Fetch options builder (fluent interface)
|
||||
class fetch_options {
|
||||
public:
|
||||
explicit fetch_options(const fl::string& url) : mUrl(url) {}
|
||||
fetch_options(const fl::string& url, const RequestOptions& options)
|
||||
: mUrl(url), mOptions(options) {}
|
||||
|
||||
/// Set HTTP method
|
||||
fetch_options& method(const fl::string& http_method) {
|
||||
mOptions.method = http_method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add header
|
||||
fetch_options& header(const fl::string& name, const fl::string& value) {
|
||||
mOptions.headers[name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set request body
|
||||
fetch_options& body(const fl::string& data) {
|
||||
mOptions.body = data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set JSON body with proper content type
|
||||
fetch_options& json(const fl::string& json_data) {
|
||||
mOptions.body = json_data;
|
||||
mOptions.headers["Content-Type"] = "application/json";
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set timeout in milliseconds
|
||||
fetch_options& timeout(int timeout_ms) {
|
||||
mOptions.timeout_ms = timeout_ms;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Get the URL for this request
|
||||
const fl::string& url() const { return mUrl; }
|
||||
|
||||
/// Get the options for this request
|
||||
const RequestOptions& options() const { return mOptions; }
|
||||
|
||||
private:
|
||||
fl::string mUrl;
|
||||
RequestOptions mOptions;
|
||||
|
||||
friend class FetchManager;
|
||||
};
|
||||
|
||||
class FetchEngineListener;
|
||||
|
||||
/// Internal fetch manager for promise tracking
|
||||
class FetchManager : public async_runner {
|
||||
public:
|
||||
static FetchManager& instance();
|
||||
|
||||
void register_promise(const fl::promise<response>& promise);
|
||||
|
||||
// async_runner interface
|
||||
void update() override;
|
||||
bool has_active_tasks() const override;
|
||||
size_t active_task_count() const override;
|
||||
|
||||
// Legacy API
|
||||
fl::size active_requests() const;
|
||||
void cleanup_completed_promises();
|
||||
|
||||
private:
|
||||
fl::vector<fl::promise<response>> mActivePromises;
|
||||
fl::unique_ptr<FetchEngineListener> mEngineListener;
|
||||
};
|
||||
|
||||
// ========== Simple Callback API (Backward Compatible) ==========
|
||||
|
||||
/// @brief Make an HTTP GET request (cross-platform, backward compatible)
|
||||
/// @param url The URL to fetch
|
||||
/// @param callback Function to call with the response
|
||||
///
|
||||
/// On WASM/browser platforms: Uses native JavaScript fetch() API
|
||||
/// On Arduino/embedded platforms: Immediately calls callback with error response
|
||||
void fetch(const fl::string& url, const FetchCallback& callback);
|
||||
|
||||
/// @brief Make an HTTP GET request with URL string literal (cross-platform)
|
||||
/// @param url The URL to fetch (C-string)
|
||||
/// @param callback Function to call with the response
|
||||
inline void fetch(const char* url, const FetchCallback& callback) {
|
||||
fetch(fl::string(url), callback);
|
||||
}
|
||||
|
||||
// ========== Promise-Based API (JavaScript-like) ==========
|
||||
|
||||
/// HTTP GET request
|
||||
fl::promise<response> fetch_get(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP POST request
|
||||
fl::promise<response> fetch_post(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP PUT request
|
||||
fl::promise<response> fetch_put(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP DELETE request
|
||||
fl::promise<response> fetch_delete(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP HEAD request
|
||||
fl::promise<response> fetch_head(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP OPTIONS request
|
||||
fl::promise<response> fetch_http_options(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP PATCH request
|
||||
fl::promise<response> fetch_patch(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// Generic request with options (like fetch(url, options))
|
||||
fl::promise<response> fetch_request(const fl::string& url, const RequestOptions& options = RequestOptions());
|
||||
|
||||
/// Legacy manual update for fetch promises (use fl::async_run() for new code)
|
||||
/// @deprecated Use fl::async_run() instead - this calls async_run() internally
|
||||
void fetch_update();
|
||||
|
||||
/// Get number of active requests
|
||||
fl::size fetch_active_requests();
|
||||
|
||||
/// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request);
|
||||
|
||||
} // namespace fl
|
||||
74
libraries/FastLED/src/fl/fft.cpp
Normal file
74
libraries/FastLED/src/fl/fft.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/fft_impl.h"
|
||||
#include "fl/hash_map_lru.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/memory.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <> struct Hash<FFT_Args> {
|
||||
fl::u32 operator()(const FFT_Args &key) const noexcept {
|
||||
return MurmurHash3_x86_32(&key, sizeof(FFT_Args));
|
||||
}
|
||||
};
|
||||
|
||||
struct FFT::HashMap : public HashMapLru<FFT_Args, fl::shared_ptr<FFTImpl>> {
|
||||
HashMap(fl::size max_size)
|
||||
: fl::HashMapLru<FFT_Args, fl::shared_ptr<FFTImpl>>(max_size) {}
|
||||
};
|
||||
|
||||
FFT::FFT() { mMap.reset(new HashMap(8)); };
|
||||
|
||||
FFT::~FFT() = default;
|
||||
|
||||
FFT::FFT(const FFT &other) {
|
||||
// copy the map
|
||||
mMap.reset();
|
||||
mMap.reset(new HashMap(*other.mMap));
|
||||
}
|
||||
|
||||
FFT &FFT::operator=(const FFT &other) {
|
||||
mMap.reset();
|
||||
mMap.reset(new HashMap(*other.mMap));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FFT::run(const span<const fl::i16> &sample, FFTBins *out,
|
||||
const FFT_Args &args) {
|
||||
FFT_Args args2 = args;
|
||||
args2.samples = sample.size();
|
||||
get_or_create(args2).run(sample, out);
|
||||
}
|
||||
|
||||
void FFT::clear() { mMap->clear(); }
|
||||
|
||||
fl::size FFT::size() const { return mMap->size(); }
|
||||
|
||||
void FFT::setFFTCacheSize(fl::size size) { mMap->setMaxSize(size); }
|
||||
|
||||
FFTImpl &FFT::get_or_create(const FFT_Args &args) {
|
||||
fl::shared_ptr<FFTImpl> *val = mMap->find_value(args);
|
||||
if (val) {
|
||||
// we have it.
|
||||
return **val;
|
||||
}
|
||||
// else we have to make a new one.
|
||||
fl::shared_ptr<FFTImpl> fft = fl::make_shared<FFTImpl>(args);
|
||||
(*mMap)[args] = fft;
|
||||
return *fft;
|
||||
}
|
||||
|
||||
bool FFT_Args::operator==(const FFT_Args &other) const {
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(float-equal);
|
||||
|
||||
return samples == other.samples && bands == other.bands &&
|
||||
fmin == other.fmin && fmax == other.fmax &&
|
||||
sample_rate == other.sample_rate;
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
125
libraries/FastLED/src/fl/fft.h
Normal file
125
libraries/FastLED/src/fl/fft.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/memfill.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class FFTImpl;
|
||||
class AudioSample;
|
||||
|
||||
struct FFTBins {
|
||||
public:
|
||||
FFTBins(fl::size n) : mSize(n) {
|
||||
bins_raw.reserve(n);
|
||||
bins_db.reserve(n);
|
||||
}
|
||||
|
||||
// Copy constructor and assignment
|
||||
FFTBins(const FFTBins &other) : bins_raw(other.bins_raw), bins_db(other.bins_db), mSize(other.mSize) {}
|
||||
FFTBins &operator=(const FFTBins &other) {
|
||||
if (this != &other) {
|
||||
mSize = other.mSize;
|
||||
bins_raw = other.bins_raw;
|
||||
bins_db = other.bins_db;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move constructor and assignment
|
||||
FFTBins(FFTBins &&other) noexcept
|
||||
: bins_raw(fl::move(other.bins_raw)), bins_db(fl::move(other.bins_db)), mSize(other.mSize) {}
|
||||
|
||||
FFTBins &operator=(FFTBins &&other) noexcept {
|
||||
if (this != &other) {
|
||||
bins_raw = fl::move(other.bins_raw);
|
||||
bins_db = fl::move(other.bins_db);
|
||||
mSize = other.mSize;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
bins_raw.clear();
|
||||
bins_db.clear();
|
||||
}
|
||||
|
||||
fl::size size() const { return mSize; }
|
||||
|
||||
// The bins are the output of the FFTImpl.
|
||||
fl::vector<float> bins_raw;
|
||||
// The frequency range of the bins.
|
||||
fl::vector<float> bins_db;
|
||||
|
||||
private:
|
||||
fl::size mSize;
|
||||
};
|
||||
|
||||
struct FFT_Args {
|
||||
static int DefaultSamples() { return 512; }
|
||||
static int DefaultBands() { return 16; }
|
||||
static float DefaultMinFrequency() { return 174.6f; }
|
||||
static float DefaultMaxFrequency() { return 4698.3f; }
|
||||
static int DefaultSampleRate() { return 44100; }
|
||||
|
||||
int samples;
|
||||
int bands;
|
||||
float fmin;
|
||||
float fmax;
|
||||
int sample_rate;
|
||||
|
||||
FFT_Args(int samples = DefaultSamples(), int bands = DefaultBands(),
|
||||
float fmin = DefaultMinFrequency(),
|
||||
float fmax = DefaultMaxFrequency(),
|
||||
int sample_rate = DefaultSampleRate()) {
|
||||
// Memset so that this object can be hashed without garbage from packed
|
||||
// in data.
|
||||
fl::memfill(this, 0, sizeof(FFT_Args));
|
||||
this->samples = samples;
|
||||
this->bands = bands;
|
||||
this->fmin = fmin;
|
||||
this->fmax = fmax;
|
||||
this->sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
// Rule of 5 for POD data
|
||||
FFT_Args(const FFT_Args &other) = default;
|
||||
FFT_Args &operator=(const FFT_Args &other) = default;
|
||||
FFT_Args(FFT_Args &&other) noexcept = default;
|
||||
FFT_Args &operator=(FFT_Args &&other) noexcept = default;
|
||||
|
||||
bool operator==(const FFT_Args &other) const ;
|
||||
bool operator!=(const FFT_Args &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
class FFT {
|
||||
public:
|
||||
FFT();
|
||||
~FFT();
|
||||
|
||||
FFT(FFT &&) = default;
|
||||
FFT &operator=(FFT &&) = default;
|
||||
FFT(const FFT & other);
|
||||
FFT &operator=(const FFT & other);
|
||||
|
||||
void run(const span<const i16> &sample, FFTBins *out,
|
||||
const FFT_Args &args = FFT_Args());
|
||||
|
||||
void clear();
|
||||
fl::size size() const;
|
||||
|
||||
// FFT's are expensive to create, so we cache them. This sets the size of
|
||||
// the cache. The default is 8.
|
||||
void setFFTCacheSize(fl::size size);
|
||||
|
||||
private:
|
||||
// Get the FFTImpl for the given arguments.
|
||||
FFTImpl &get_or_create(const FFT_Args &args);
|
||||
struct HashMap;
|
||||
scoped_ptr<HashMap> mMap;
|
||||
};
|
||||
|
||||
}; // namespace fl
|
||||
171
libraries/FastLED/src/fl/fft_impl.cpp
Normal file
171
libraries/FastLED/src/fl/fft_impl.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/// #include <Arduino.h>
|
||||
// #include <iostream>
|
||||
// #include "audio_types.h"
|
||||
// // #include "defs.h"
|
||||
// #include "thirdparty/cq_kernel/cq_kernel.h"
|
||||
// #include "thirdparty/cq_kernel/kiss_fftr.h"
|
||||
// #include "util.h"
|
||||
|
||||
#ifndef FASTLED_INTERNAL
|
||||
#define FASTLED_INTERNAL 1
|
||||
#endif
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "third_party/cq_kernel/cq_kernel.h"
|
||||
#include "third_party/cq_kernel/kiss_fftr.h"
|
||||
|
||||
#include "fl/array.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/fft.h"
|
||||
#include "fl/fft_impl.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
#include "fl/memfill.h"
|
||||
|
||||
#define AUDIO_SAMPLE_RATE 44100
|
||||
#define SAMPLES 512
|
||||
#define BANDS 16
|
||||
#define SAMPLING_FREQUENCY AUDIO_SAMPLE_RATE
|
||||
#define MAX_FREQUENCY 4698.3
|
||||
#define MIN_FREQUENCY 174.6
|
||||
#define MIN_VAL 5000 // Equivalent to 0.15 in Q15
|
||||
|
||||
#define PRINT_HEADER 1
|
||||
|
||||
namespace fl {
|
||||
|
||||
class FFTContext {
|
||||
public:
|
||||
FFTContext(int samples, int bands, float fmin, float fmax, int sample_rate)
|
||||
: m_fftr_cfg(nullptr), m_kernels(nullptr) {
|
||||
fl::memfill(&m_cq_cfg, 0, sizeof(m_cq_cfg));
|
||||
m_cq_cfg.samples = samples;
|
||||
m_cq_cfg.bands = bands;
|
||||
m_cq_cfg.fmin = fmin;
|
||||
m_cq_cfg.fmax = fmax;
|
||||
m_cq_cfg.fs = sample_rate;
|
||||
m_cq_cfg.min_val = MIN_VAL;
|
||||
m_fftr_cfg = kiss_fftr_alloc(samples, 0, NULL, NULL);
|
||||
if (!m_fftr_cfg) {
|
||||
FASTLED_WARN("Failed to allocate FFTImpl context");
|
||||
return;
|
||||
}
|
||||
m_kernels = generate_kernels(m_cq_cfg);
|
||||
}
|
||||
~FFTContext() {
|
||||
if (m_fftr_cfg) {
|
||||
kiss_fftr_free(m_fftr_cfg);
|
||||
}
|
||||
if (m_kernels) {
|
||||
free_kernels(m_kernels, m_cq_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
fl::size sampleSize() const { return m_cq_cfg.samples; }
|
||||
|
||||
void fft_unit_test(span<const i16> buffer, FFTBins *out) {
|
||||
|
||||
// FASTLED_ASSERT(512 == m_cq_cfg.samples, "FFTImpl samples mismatch and
|
||||
// are still hardcoded to 512");
|
||||
out->clear();
|
||||
// allocate
|
||||
FASTLED_STACK_ARRAY(kiss_fft_cpx, fft, m_cq_cfg.samples);
|
||||
FASTLED_STACK_ARRAY(kiss_fft_cpx, cq, m_cq_cfg.bands);
|
||||
// initialize
|
||||
kiss_fftr(m_fftr_cfg, buffer.data(), fft);
|
||||
apply_kernels(fft, cq, m_kernels, m_cq_cfg);
|
||||
const float maxf = m_cq_cfg.fmax;
|
||||
const float minf = m_cq_cfg.fmin;
|
||||
const float delta_f = (maxf - minf) / m_cq_cfg.bands;
|
||||
// begin transform
|
||||
for (int i = 0; i < m_cq_cfg.bands; ++i) {
|
||||
i32 real = cq[i].r;
|
||||
i32 imag = cq[i].i;
|
||||
float r2 = float(real * real);
|
||||
float i2 = float(imag * imag);
|
||||
float magnitude = sqrt(r2 + i2);
|
||||
float magnitude_db = 20 * log10(magnitude);
|
||||
float f_start = minf + i * delta_f;
|
||||
float f_end = f_start + delta_f;
|
||||
FASTLED_UNUSED(f_start);
|
||||
FASTLED_UNUSED(f_end);
|
||||
|
||||
if (magnitude <= 0.0f) {
|
||||
magnitude_db = 0.0f;
|
||||
}
|
||||
|
||||
// FASTLED_UNUSED(magnitude_db);
|
||||
// FASTLED_WARN("magnitude_db: " << magnitude_db);
|
||||
// out->push_back(magnitude_db);
|
||||
out->bins_raw.push_back(magnitude);
|
||||
out->bins_db.push_back(magnitude_db);
|
||||
}
|
||||
}
|
||||
|
||||
fl::string info() const {
|
||||
// Calculate frequency delta
|
||||
float delta_f = (m_cq_cfg.fmax - m_cq_cfg.fmin) / m_cq_cfg.bands;
|
||||
fl::StrStream ss;
|
||||
ss << "FFTImpl Frequency Bands: ";
|
||||
|
||||
for (int i = 0; i < m_cq_cfg.bands; ++i) {
|
||||
float f_start = m_cq_cfg.fmin + i * delta_f;
|
||||
float f_end = f_start + delta_f;
|
||||
ss << f_start << "Hz-" << f_end << "Hz, ";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
kiss_fftr_cfg m_fftr_cfg;
|
||||
cq_kernels_t m_kernels;
|
||||
cq_kernel_cfg m_cq_cfg;
|
||||
};
|
||||
|
||||
FFTImpl::FFTImpl(const FFT_Args &args) {
|
||||
mContext.reset(new FFTContext(args.samples, args.bands, args.fmin,
|
||||
args.fmax, args.sample_rate));
|
||||
}
|
||||
|
||||
FFTImpl::~FFTImpl() { mContext.reset(); }
|
||||
|
||||
fl::string FFTImpl::info() const {
|
||||
if (mContext) {
|
||||
return mContext->info();
|
||||
} else {
|
||||
FASTLED_WARN("FFTImpl context is not initialized");
|
||||
return fl::string();
|
||||
}
|
||||
}
|
||||
|
||||
fl::size FFTImpl::sampleSize() const {
|
||||
if (mContext) {
|
||||
return mContext->sampleSize();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FFTImpl::Result FFTImpl::run(const AudioSample &sample, FFTBins *out) {
|
||||
auto &audio_sample = sample.pcm();
|
||||
span<const i16> slice(audio_sample);
|
||||
return run(slice, out);
|
||||
}
|
||||
|
||||
FFTImpl::Result FFTImpl::run(span<const i16> sample, FFTBins *out) {
|
||||
if (!mContext) {
|
||||
return FFTImpl::Result(false, "FFTImpl context is not initialized");
|
||||
}
|
||||
if (sample.size() != mContext->sampleSize()) {
|
||||
FASTLED_WARN("FFTImpl sample size mismatch");
|
||||
return FFTImpl::Result(false, "FFTImpl sample size mismatch");
|
||||
}
|
||||
mContext->fft_unit_test(sample, out);
|
||||
return FFTImpl::Result(true, "");
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
58
libraries/FastLED/src/fl/fft_impl.h
Normal file
58
libraries/FastLED/src/fl/fft_impl.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/hash_map_lru.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class AudioSample;
|
||||
class FFTContext;
|
||||
struct FFT_Args;
|
||||
|
||||
// Example:
|
||||
// FFTImpl fft(512, 16);
|
||||
// auto sample = SINE WAVE OF 512 SAMPLES
|
||||
// fft.run(buffer, &out);
|
||||
// FASTLED_WARN("FFTImpl output: " << out); // 16 bands of output.
|
||||
class FFTImpl {
|
||||
public:
|
||||
// Result indicating success or failure of the FFTImpl run (in which case
|
||||
// there will be an error message).
|
||||
struct Result {
|
||||
Result(bool ok, const string &error) : ok(ok), error(error) {}
|
||||
bool ok = false;
|
||||
fl::string error;
|
||||
};
|
||||
// Default values for the FFTImpl.
|
||||
FFTImpl(const FFT_Args &args);
|
||||
~FFTImpl();
|
||||
|
||||
fl::size sampleSize() const;
|
||||
// Note that the sample sizes MUST match the samples size passed into the
|
||||
// constructor.
|
||||
Result run(const AudioSample &sample, FFTBins *out);
|
||||
Result run(span<const i16> sample, FFTBins *out);
|
||||
// Info on what the frequency the bins represent
|
||||
fl::string info() const;
|
||||
|
||||
// Detail.
|
||||
static int DefaultSamples() { return 512; }
|
||||
static int DefaultBands() { return 16; }
|
||||
static float DefaultMinFrequency() { return 174.6f; }
|
||||
static float DefaultMaxFrequency() { return 4698.3f; }
|
||||
static int DefaultSampleRate() { return 44100; }
|
||||
|
||||
// Disable copy and move constructors and assignment operators
|
||||
FFTImpl(const FFTImpl &) = delete;
|
||||
FFTImpl &operator=(const FFTImpl &) = delete;
|
||||
FFTImpl(FFTImpl &&) = delete;
|
||||
FFTImpl &operator=(FFTImpl &&) = delete;
|
||||
|
||||
private:
|
||||
fl::unique_ptr<FFTContext> mContext;
|
||||
};
|
||||
|
||||
}; // namespace fl
|
||||
206
libraries/FastLED/src/fl/file_system.cpp
Normal file
206
libraries/FastLED/src/fl/file_system.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "fl/file_system.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/has_include.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "platforms/wasm/fs_wasm.h"
|
||||
#define FASTLED_HAS_SDCARD 1
|
||||
#elif FL_HAS_INCLUDE(<SD.h>) && FL_HAS_INCLUDE(<fs.h>)
|
||||
// Include Arduino SD card implementation when SD library is available
|
||||
#include "platforms/fs_sdcard_arduino.hpp"
|
||||
#define FASTLED_HAS_SDCARD 1
|
||||
#else
|
||||
#define FASTLED_HAS_SDCARD 0
|
||||
#endif
|
||||
|
||||
#include "fl/json.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/unused.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class NullFileHandle : public FileHandle {
|
||||
public:
|
||||
NullFileHandle() = default;
|
||||
~NullFileHandle() override {}
|
||||
|
||||
bool available() const override { return false; }
|
||||
fl::size size() const override { return 0; }
|
||||
fl::size read(u8 *dst, fl::size bytesToRead) override {
|
||||
FASTLED_UNUSED(dst);
|
||||
FASTLED_UNUSED(bytesToRead);
|
||||
return 0;
|
||||
}
|
||||
fl::size pos() const override { return 0; }
|
||||
const char *path() const override { return "NULL FILE HANDLE"; }
|
||||
bool seek(fl::size pos) override {
|
||||
FASTLED_UNUSED(pos);
|
||||
return false;
|
||||
}
|
||||
void close() override {}
|
||||
bool valid() const override {
|
||||
FASTLED_WARN("NullFileHandle is not valid");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class NullFileSystem : public FsImpl {
|
||||
public:
|
||||
NullFileSystem() {
|
||||
FASTLED_WARN("NullFileSystem instantiated as a placeholder, please "
|
||||
"implement a file system for your platform.");
|
||||
}
|
||||
~NullFileSystem() override {}
|
||||
|
||||
bool begin() override { return true; }
|
||||
void end() override {}
|
||||
|
||||
void close(FileHandlePtr file) override {
|
||||
// No need to do anything for in-memory files
|
||||
FASTLED_UNUSED(file);
|
||||
FASTLED_WARN("NullFileSystem::close");
|
||||
}
|
||||
|
||||
FileHandlePtr openRead(const char *_path) override {
|
||||
FASTLED_UNUSED(_path);
|
||||
fl::shared_ptr<NullFileHandle> ptr = fl::make_shared<NullFileHandle>();
|
||||
FileHandlePtr out = ptr;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
bool FileSystem::beginSd(int cs_pin) {
|
||||
mFs = make_sdcard_filesystem(cs_pin);
|
||||
if (!mFs) {
|
||||
return false;
|
||||
}
|
||||
mFs->begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::begin(FsImplPtr platform_filesystem) {
|
||||
mFs = platform_filesystem;
|
||||
if (!mFs) {
|
||||
return false;
|
||||
}
|
||||
mFs->begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
fl::size FileHandle::bytesLeft() const { return size() - pos(); }
|
||||
|
||||
FileSystem::FileSystem() : mFs() {}
|
||||
|
||||
void FileSystem::end() {
|
||||
if (mFs) {
|
||||
mFs->end();
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSystem::readJson(const char *path, Json *doc) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse using the new Json class
|
||||
*doc = fl::Json::parse(text);
|
||||
return !doc->is_null();
|
||||
}
|
||||
|
||||
bool FileSystem::readScreenMaps(const char *path,
|
||||
fl::fl_map<string, ScreenMap> *out, string *error) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
FASTLED_WARN("Failed to read file: " << path);
|
||||
if (error) {
|
||||
*error = "Failed to read file: ";
|
||||
error->append(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
string err;
|
||||
bool ok = ScreenMap::ParseJson(text.c_str(), out, &err);
|
||||
if (!ok) {
|
||||
FASTLED_WARN("Failed to parse screen map: " << err.c_str());
|
||||
*error = err;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::readScreenMap(const char *path, const char *name,
|
||||
ScreenMap *out, string *error) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
FASTLED_WARN("Failed to read file: " << path);
|
||||
if (error) {
|
||||
*error = "Failed to read file: ";
|
||||
error->append(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
string err;
|
||||
bool ok = ScreenMap::ParseJson(text.c_str(), name, out, &err);
|
||||
if (!ok) {
|
||||
FASTLED_WARN("Failed to parse screen map: " << err.c_str());
|
||||
*error = err;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSystem::close(FileHandlePtr file) { mFs->close(file); }
|
||||
|
||||
FileHandlePtr FileSystem::openRead(const char *path) {
|
||||
return mFs->openRead(path);
|
||||
}
|
||||
Video FileSystem::openVideo(const char *path, fl::size pixelsPerFrame, float fps,
|
||||
fl::size nFrameHistory) {
|
||||
Video video(pixelsPerFrame, fps, nFrameHistory);
|
||||
FileHandlePtr file = openRead(path);
|
||||
if (!file) {
|
||||
video.setError(fl::string("Could not open file: ").append(path));
|
||||
return video;
|
||||
}
|
||||
video.begin(file);
|
||||
return video;
|
||||
}
|
||||
|
||||
bool FileSystem::readText(const char *path, fl::string *out) {
|
||||
FileHandlePtr file = openRead(path);
|
||||
if (!file) {
|
||||
FASTLED_WARN("Failed to open file: " << path);
|
||||
return false;
|
||||
}
|
||||
fl::size size = file->size();
|
||||
out->reserve(size + out->size());
|
||||
bool wrote = false;
|
||||
while (file->available()) {
|
||||
u8 buf[64];
|
||||
fl::size n = file->read(buf, sizeof(buf));
|
||||
// out->append(buf, n);
|
||||
out->append((const char *)buf, n);
|
||||
wrote = true;
|
||||
}
|
||||
file->close();
|
||||
FASTLED_DBG_IF(!wrote, "Failed to write any data to the output string.");
|
||||
return wrote;
|
||||
}
|
||||
} // namespace fl
|
||||
|
||||
namespace fl {
|
||||
#if !FASTLED_HAS_SDCARD
|
||||
// Weak fallback implementation when SD library is not available
|
||||
FL_LINK_WEAK FsImplPtr make_sdcard_filesystem(int cs_pin) {
|
||||
FASTLED_UNUSED(cs_pin);
|
||||
fl::shared_ptr<NullFileSystem> ptr = fl::make_shared<NullFileSystem>();
|
||||
FsImplPtr out = ptr;
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace fl
|
||||
108
libraries/FastLED/src/fl/file_system.h
Normal file
108
libraries/FastLED/src/fl/file_system.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
// Note, fs.h breaks ESPAsyncWebServer so we use file_system.h instead.
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/str.h"
|
||||
#include "fx/video.h"
|
||||
#include "fl/screenmap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(FsImpl);
|
||||
// PLATFORM INTERFACE
|
||||
// You need to define this for your platform.
|
||||
// Otherwise a null filesystem will be used that will do nothing but spew
|
||||
// warnings, but otherwise won't crash the system.
|
||||
FsImplPtr make_sdcard_filesystem(int cs_pin);
|
||||
} // namespace fl
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
struct CRGB;
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
namespace fl {
|
||||
|
||||
class ScreenMap;
|
||||
FASTLED_SMART_PTR(FileSystem);
|
||||
FASTLED_SMART_PTR(FileHandle);
|
||||
class Video;
|
||||
template <typename Key, typename Value, fl::size N> class FixedMap;
|
||||
|
||||
namespace json2 {
|
||||
class Json;
|
||||
}
|
||||
|
||||
class FileSystem {
|
||||
public:
|
||||
FileSystem();
|
||||
bool beginSd(int cs_pin); // Signal to begin using the filesystem resource.
|
||||
bool begin(FsImplPtr platform_filesystem); // Signal to begin using the
|
||||
// filesystem resource.
|
||||
void end(); // Signal to end use of the file system.
|
||||
|
||||
FileHandlePtr
|
||||
openRead(const char *path); // Null if file could not be opened.
|
||||
Video
|
||||
openVideo(const char *path, fl::size pixelsPerFrame, float fps = 30.0f,
|
||||
fl::size nFrameHistory = 0); // Null if video could not be opened.
|
||||
bool readText(const char *path, string *out);
|
||||
bool readJson(const char *path, Json *doc);
|
||||
bool readScreenMaps(const char *path, fl::fl_map<string, ScreenMap> *out,
|
||||
string *error = nullptr);
|
||||
bool readScreenMap(const char *path, const char *name, ScreenMap *out,
|
||||
string *error = nullptr);
|
||||
void close(FileHandlePtr file);
|
||||
|
||||
private:
|
||||
FsImplPtr mFs; // System dependent filesystem.
|
||||
};
|
||||
|
||||
// An abstract class that represents a file handle.
|
||||
// Devices like the SD card will return one of these.
|
||||
class FileHandle {
|
||||
public:
|
||||
virtual ~FileHandle() {}
|
||||
virtual bool available() const = 0;
|
||||
virtual fl::size bytesLeft() const;
|
||||
virtual fl::size size() const = 0;
|
||||
virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0;
|
||||
virtual fl::size pos() const = 0;
|
||||
virtual const char *path() const = 0;
|
||||
virtual bool seek(fl::size pos) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual bool valid() const = 0;
|
||||
|
||||
// convenience functions
|
||||
fl::size readCRGB(CRGB *dst, fl::size n) {
|
||||
return read((fl::u8 *)dst, n * 3) / 3;
|
||||
}
|
||||
};
|
||||
|
||||
// Platforms will subclass this to implement the filesystem.
|
||||
class FsImpl {
|
||||
public:
|
||||
struct Visitor {
|
||||
virtual ~Visitor() {}
|
||||
virtual void accept(const char *path) = 0;
|
||||
};
|
||||
FsImpl() = default;
|
||||
virtual ~FsImpl() {} // Use default pins for spi.
|
||||
virtual bool begin() = 0;
|
||||
// End use of card
|
||||
virtual void end() = 0;
|
||||
virtual void close(FileHandlePtr file) = 0;
|
||||
virtual FileHandlePtr openRead(const char *path) = 0;
|
||||
|
||||
virtual bool ls(Visitor &visitor) {
|
||||
// todo: implement.
|
||||
(void)visitor;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
173
libraries/FastLED/src/fl/fill.cpp
Normal file
173
libraries/FastLED/src/fl/fill.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fill.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
void fill_solid(struct CRGB *targetArray, int numToFill,
|
||||
const struct CRGB &color) {
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_solid(struct CHSV *targetArray, int numToFill,
|
||||
const struct CHSV &color) {
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
// void fill_solid( struct CRGB* targetArray, int numToFill,
|
||||
// const struct CHSV& hsvColor)
|
||||
// {
|
||||
// fill_solid<CRGB>( targetArray, numToFill, (CRGB) hsvColor);
|
||||
// }
|
||||
|
||||
void fill_rainbow(struct CRGB *targetArray, int numToFill, u8 initialhue,
|
||||
u8 deltahue) {
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
hsv.hue += deltahue;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow(struct CHSV *targetArray, int numToFill, u8 initialhue,
|
||||
u8 deltahue) {
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
hsv.hue += deltahue;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow_circular(struct CRGB *targetArray, int numToFill,
|
||||
u8 initialhue, bool reversed) {
|
||||
if (numToFill == 0)
|
||||
return; // avoiding div/0
|
||||
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
|
||||
const u16 hueChange =
|
||||
65535 / (u16)numToFill; // hue change for each LED, * 256 for
|
||||
// precision (256 * 256 - 1)
|
||||
u16 hueOffset = 0; // offset for hue value, with precision (*256)
|
||||
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
if (reversed)
|
||||
hueOffset -= hueChange;
|
||||
else
|
||||
hueOffset += hueChange;
|
||||
hsv.hue = initialhue +
|
||||
(u8)(hueOffset >>
|
||||
8); // assign new hue with precise offset (as 8-bit)
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow_circular(struct CHSV *targetArray, int numToFill,
|
||||
u8 initialhue, bool reversed) {
|
||||
if (numToFill == 0)
|
||||
return; // avoiding div/0
|
||||
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
|
||||
const u16 hueChange =
|
||||
65535 / (u16)numToFill; // hue change for each LED, * 256 for
|
||||
// precision (256 * 256 - 1)
|
||||
u16 hueOffset = 0; // offset for hue value, with precision (*256)
|
||||
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
if (reversed)
|
||||
hueOffset -= hueChange;
|
||||
else
|
||||
hueOffset += hueChange;
|
||||
hsv.hue = initialhue +
|
||||
(u8)(hueOffset >>
|
||||
8); // assign new hue with precise offset (as 8-bit)
|
||||
}
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor,
|
||||
u16 endpos, CRGB endcolor) {
|
||||
// if the points are in the wrong order, straighten them
|
||||
if (endpos < startpos) {
|
||||
u16 t = endpos;
|
||||
CRGB tc = endcolor;
|
||||
endcolor = startcolor;
|
||||
endpos = startpos;
|
||||
startpos = t;
|
||||
startcolor = tc;
|
||||
}
|
||||
|
||||
saccum87 rdistance87;
|
||||
saccum87 gdistance87;
|
||||
saccum87 bdistance87;
|
||||
|
||||
rdistance87 = (endcolor.r - startcolor.r) << 7;
|
||||
gdistance87 = (endcolor.g - startcolor.g) << 7;
|
||||
bdistance87 = (endcolor.b - startcolor.b) << 7;
|
||||
|
||||
u16 pixeldistance = endpos - startpos;
|
||||
i16 divisor = pixeldistance ? pixeldistance : 1;
|
||||
|
||||
saccum87 rdelta87 = rdistance87 / divisor;
|
||||
saccum87 gdelta87 = gdistance87 / divisor;
|
||||
saccum87 bdelta87 = bdistance87 / divisor;
|
||||
|
||||
rdelta87 *= 2;
|
||||
gdelta87 *= 2;
|
||||
bdelta87 *= 2;
|
||||
|
||||
accum88 r88 = startcolor.r << 8;
|
||||
accum88 g88 = startcolor.g << 8;
|
||||
accum88 b88 = startcolor.b << 8;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
leds[i] = CRGB(r88 >> 8, g88 >> 8, b88 >> 8);
|
||||
r88 += rdelta87;
|
||||
g88 += gdelta87;
|
||||
b88 += bdelta87;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2) {
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, last, c2);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3) {
|
||||
u16 half = (numLeds / 2);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, half, c2);
|
||||
fill_gradient_RGB(leds, half, c2, last, c3);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3, const CRGB &c4) {
|
||||
u16 onethird = (numLeds / 3);
|
||||
u16 twothirds = ((numLeds * 2) / 3);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, onethird, c2);
|
||||
fill_gradient_RGB(leds, onethird, c2, twothirds, c3);
|
||||
fill_gradient_RGB(leds, twothirds, c3, last, c4);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
291
libraries/FastLED/src/fl/fill.h
Normal file
291
libraries/FastLED/src/fl/fill.h
Normal file
@@ -0,0 +1,291 @@
|
||||
#pragma once
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/colorutils_misc.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
FL_DISABLE_WARNING_RETURN_TYPE
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
|
||||
/// ANSI: signed short _Accum. 8 bits int, 7 bits fraction
|
||||
/// @see accum88
|
||||
#define saccum87 i16
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @defgroup ColorUtils Color Utility Functions
|
||||
/// A variety of functions for working with color, palettes, and leds
|
||||
/// @{
|
||||
|
||||
/// @defgroup ColorFills Color Fill Functions
|
||||
/// Functions for filling LED arrays with colors and gradients
|
||||
/// @{
|
||||
|
||||
/// Fill a range of LEDs with a solid color.
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param color the color to fill with
|
||||
void fill_solid(struct CRGB *targetArray, int numToFill,
|
||||
const struct CRGB &color);
|
||||
|
||||
/// @copydoc fill_solid()
|
||||
void fill_solid(struct CHSV *targetArray, int numToFill,
|
||||
const struct CHSV &color);
|
||||
|
||||
/// Fill a range of LEDs with a rainbow of colors.
|
||||
/// The colors making up the rainbow are at full saturation and full
|
||||
/// value (brightness).
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param initialhue the starting hue for the rainbow
|
||||
/// @param deltahue how many hue values to advance for each LED
|
||||
void fill_rainbow(struct CRGB *targetArray, int numToFill, fl::u8 initialhue,
|
||||
fl::u8 deltahue = 5);
|
||||
|
||||
/// @copydoc fill_rainbow()
|
||||
void fill_rainbow(struct CHSV *targetArray, int numToFill, fl::u8 initialhue,
|
||||
fl::u8 deltahue = 5);
|
||||
|
||||
/// Fill a range of LEDs with a rainbow of colors, so that the hues
|
||||
/// are continuous between the end of the strip and the beginning.
|
||||
/// The colors making up the rainbow are at full saturation and full
|
||||
/// value (brightness).
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param initialhue the starting hue for the rainbow
|
||||
/// @param reversed whether to progress through the rainbow hues backwards
|
||||
void fill_rainbow_circular(struct CRGB *targetArray, int numToFill,
|
||||
fl::u8 initialhue, bool reversed = false);
|
||||
|
||||
/// @copydoc fill_rainbow_circular()
|
||||
void fill_rainbow_circular(struct CHSV *targetArray, int numToFill,
|
||||
fl::u8 initialhue, bool reversed = false);
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
|
||||
/// This function can write the gradient colors either:
|
||||
///
|
||||
/// 1. Into an array of CRGBs (e.g., an leds[] array, or a CRGB palette)
|
||||
/// 2. Into an array of CHSVs (e.g. a CHSV palette).
|
||||
///
|
||||
/// In the case of writing into a CRGB array, the gradient is
|
||||
/// computed in HSV space, and then HSV values are converted to RGB
|
||||
/// as they're written into the CRGB array.
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param startpos the starting position in the array
|
||||
/// @param startcolor the starting color for the gradient
|
||||
/// @param endpos the ending position in the array
|
||||
/// @param endcolor the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 startpos, CHSV startcolor,
|
||||
u16 endpos, CHSV endcolor,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
// if the points are in the wrong order, straighten them
|
||||
if (endpos < startpos) {
|
||||
u16 t = endpos;
|
||||
CHSV tc = endcolor;
|
||||
endcolor = startcolor;
|
||||
endpos = startpos;
|
||||
startpos = t;
|
||||
startcolor = tc;
|
||||
}
|
||||
|
||||
// If we're fading toward black (val=0) or white (sat=0),
|
||||
// then set the endhue to the starthue.
|
||||
// This lets us ramp smoothly to black or white, regardless
|
||||
// of what 'hue' was set in the endcolor (since it doesn't matter)
|
||||
if (endcolor.value == 0 || endcolor.saturation == 0) {
|
||||
endcolor.hue = startcolor.hue;
|
||||
}
|
||||
|
||||
// Similarly, if we're fading in from black (val=0) or white (sat=0)
|
||||
// then set the starthue to the endhue.
|
||||
// This lets us ramp smoothly up from black or white, regardless
|
||||
// of what 'hue' was set in the startcolor (since it doesn't matter)
|
||||
if (startcolor.value == 0 || startcolor.saturation == 0) {
|
||||
startcolor.hue = endcolor.hue;
|
||||
}
|
||||
|
||||
saccum87 huedistance87;
|
||||
saccum87 satdistance87;
|
||||
saccum87 valdistance87;
|
||||
|
||||
satdistance87 = (endcolor.sat - startcolor.sat) << 7;
|
||||
valdistance87 = (endcolor.val - startcolor.val) << 7;
|
||||
|
||||
fl::u8 huedelta8 = endcolor.hue - startcolor.hue;
|
||||
|
||||
if (directionCode == SHORTEST_HUES) {
|
||||
directionCode = FORWARD_HUES;
|
||||
if (huedelta8 > 127) {
|
||||
directionCode = BACKWARD_HUES;
|
||||
}
|
||||
}
|
||||
|
||||
if (directionCode == LONGEST_HUES) {
|
||||
directionCode = FORWARD_HUES;
|
||||
if (huedelta8 < 128) {
|
||||
directionCode = BACKWARD_HUES;
|
||||
}
|
||||
}
|
||||
|
||||
if (directionCode == FORWARD_HUES) {
|
||||
huedistance87 = huedelta8 << 7;
|
||||
} else /* directionCode == BACKWARD_HUES */
|
||||
{
|
||||
huedistance87 = (fl::u8)(256 - huedelta8) << 7;
|
||||
huedistance87 = -huedistance87;
|
||||
}
|
||||
|
||||
u16 pixeldistance = endpos - startpos;
|
||||
i16 divisor = pixeldistance ? pixeldistance : 1;
|
||||
|
||||
#if FASTLED_USE_32_BIT_GRADIENT_FILL
|
||||
// Use higher precision 32 bit math for new micros.
|
||||
i32 huedelta823 = (huedistance87 * 65536) / divisor;
|
||||
i32 satdelta823 = (satdistance87 * 65536) / divisor;
|
||||
i32 valdelta823 = (valdistance87 * 65536) / divisor;
|
||||
|
||||
huedelta823 *= 2;
|
||||
satdelta823 *= 2;
|
||||
valdelta823 *= 2;
|
||||
u32 hue824 = static_cast<u32>(startcolor.hue) << 24;
|
||||
u32 sat824 = static_cast<u32>(startcolor.sat) << 24;
|
||||
u32 val824 = static_cast<u32>(startcolor.val) << 24;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
targetArray[i] = CHSV(hue824 >> 24, sat824 >> 24, val824 >> 24);
|
||||
hue824 += huedelta823;
|
||||
sat824 += satdelta823;
|
||||
val824 += valdelta823;
|
||||
}
|
||||
#else
|
||||
// Use 8-bit math for older micros.
|
||||
saccum87 huedelta87 = huedistance87 / divisor;
|
||||
saccum87 satdelta87 = satdistance87 / divisor;
|
||||
saccum87 valdelta87 = valdistance87 / divisor;
|
||||
|
||||
huedelta87 *= 2;
|
||||
satdelta87 *= 2;
|
||||
valdelta87 *= 2;
|
||||
|
||||
accum88 hue88 = startcolor.hue << 8;
|
||||
accum88 sat88 = startcolor.sat << 8;
|
||||
accum88 val88 = startcolor.val << 8;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
targetArray[i] = CHSV(hue88 >> 8, sat88 >> 8, val88 >> 8);
|
||||
hue88 += huedelta87;
|
||||
sat88 += satdelta87;
|
||||
val88 += valdelta87;
|
||||
}
|
||||
#endif // defined(__AVR__)
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, last, c2, directionCode);
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between three HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the middle color for the gradient
|
||||
/// @param c3 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2, const CHSV &c3,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 half = (numLeds / 2);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, half, c2, directionCode);
|
||||
fill_gradient(targetArray, half, c2, last, c3, directionCode);
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between four HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the first middle color for the gradient
|
||||
/// @param c3 the second middle color for the gradient
|
||||
/// @param c4 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2, const CHSV &c3, const CHSV &c4,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 onethird = (numLeds / 3);
|
||||
u16 twothirds = ((numLeds * 2) / 3);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, onethird, c2, directionCode);
|
||||
fill_gradient(targetArray, onethird, c2, twothirds, c3, directionCode);
|
||||
fill_gradient(targetArray, twothirds, c3, last, c4, directionCode);
|
||||
}
|
||||
|
||||
/// Convenience synonym
|
||||
#define fill_gradient_HSV fill_gradient
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors.
|
||||
/// Unlike HSV, there is no "color wheel" in RGB space, and therefore there's
|
||||
/// only one "direction" for the gradient to go. This means there's no
|
||||
/// TGradientDirectionCode parameter for direction.
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param startpos the starting position in the array
|
||||
/// @param startcolor the starting color for the gradient
|
||||
/// @param endpos the ending position in the array
|
||||
/// @param endcolor the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor,
|
||||
u16 endpos, CRGB endcolor);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between three RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the middle color for the gradient
|
||||
/// @param c3 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between four RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the first middle color for the gradient
|
||||
/// @param c3 the second middle color for the gradient
|
||||
/// @param c4 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3, const CRGB &c4);
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
191
libraries/FastLED/src/fl/five_bit_hd_gamma.h
Normal file
191
libraries/FastLED/src/fl/five_bit_hd_gamma.h
Normal file
@@ -0,0 +1,191 @@
|
||||
/// @file five_bit_hd_gamma.h
|
||||
/// Declares functions for five-bit gamma correction
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/gamma.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/math.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "lib8tion/scale8.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
enum FiveBitGammaCorrectionMode {
|
||||
kFiveBitGammaCorrectionMode_Null = 0,
|
||||
kFiveBitGammaCorrectionMode_BitShift = 1
|
||||
};
|
||||
|
||||
// Applies gamma correction for the RGBV(8, 8, 8, 5) color space, where
|
||||
// the last byte is the brightness byte at 5 bits.
|
||||
// To override this five_bit_hd_gamma_bitshift you'll need to define
|
||||
// FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE in your build settings
|
||||
// then define the function anywhere in your project.
|
||||
// Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_bitshift(
|
||||
// fl::u8 r8, fl::u8 g8, fl::u8 b8,
|
||||
// fl::u8 r8_scale, fl::u8 g8_scale, fl::u8 b8_scale,
|
||||
// fl::u8* out_r8,
|
||||
// fl::u8* out_g8,
|
||||
// fl::u8* out_b8,
|
||||
// fl::u8* out_power_5bit) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
|
||||
// Force push
|
||||
|
||||
void internal_builtin_five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit);
|
||||
|
||||
// Exposed for testing.
|
||||
void five_bit_bitshift(u16 r16, u16 g16, u16 b16, fl::u8 brightness, CRGB *out,
|
||||
fl::u8 *out_power_5bit);
|
||||
|
||||
#ifdef FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE
|
||||
// This function is located somewhere else in your project, so it's declared
|
||||
// extern here.
|
||||
extern void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit);
|
||||
#else
|
||||
inline void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit) {
|
||||
internal_builtin_five_bit_hd_gamma_bitshift(
|
||||
colors, colors_scale, global_brightness, out_colors, out_power_5bit);
|
||||
}
|
||||
#endif // FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE
|
||||
|
||||
// Simple gamma correction function that converts from
|
||||
// 8-bit color component and converts it to gamma corrected 16-bit
|
||||
// color component. Fast and no memory overhead!
|
||||
// To override this function you'll need to define
|
||||
// FASTLED_FIVE_BIT_HD_GAMMA_BITSHIFT_FUNCTION_OVERRIDE in your build settings
|
||||
// and then define your own version anywhere in your project. Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_function(
|
||||
// fl::u8 r8, fl::u8 g8, fl::u8 b8,
|
||||
// u16* r16, u16* g16, u16* b16) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
#ifdef FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE
|
||||
// This function is located somewhere else in your project, so it's declared
|
||||
// extern here.
|
||||
extern void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16,
|
||||
u16 *b16);
|
||||
#else
|
||||
inline void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16,
|
||||
u16 *b16) {
|
||||
|
||||
gamma16(color, r16, g16, b16);
|
||||
}
|
||||
#endif // FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE
|
||||
|
||||
inline void internal_builtin_five_bit_hd_gamma_bitshift(
|
||||
CRGB colors, CRGB colors_scale, fl::u8 global_brightness, CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit) {
|
||||
|
||||
if (global_brightness == 0) {
|
||||
*out_colors = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Gamma Correction
|
||||
u16 r16, g16, b16;
|
||||
five_bit_hd_gamma_function(colors, &r16, &g16, &b16);
|
||||
|
||||
// Step 2: Color correction step comes after gamma correction. These values
|
||||
// are assumed to be be relatively close to 255.
|
||||
if (colors_scale.r != 0xff) {
|
||||
r16 = scale16by8(r16, colors_scale.r);
|
||||
}
|
||||
if (colors_scale.g != 0xff) {
|
||||
g16 = scale16by8(g16, colors_scale.g);
|
||||
}
|
||||
if (colors_scale.b != 0xff) {
|
||||
b16 = scale16by8(b16, colors_scale.b);
|
||||
}
|
||||
|
||||
five_bit_bitshift(r16, g16, b16, global_brightness, out_colors,
|
||||
out_power_5bit);
|
||||
}
|
||||
|
||||
// Since the return value wasn't used, it has been omitted.
|
||||
// It's not clear what scale brightness is, or how it is to be applied,
|
||||
// so we assume 8 bits applied over the given rgb values.
|
||||
inline void five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16,
|
||||
uint8_t brightness, CRGB *out,
|
||||
uint8_t *out_power_5bit) {
|
||||
|
||||
// NEW in 3.10.2: A new closed form solution has been found!
|
||||
// Thank you https://github.com/gwgill!
|
||||
// It's okay if you don't know how this works, few do, but it tests
|
||||
// very well and is better than the old iterative approach which had
|
||||
// bad quantization issues (sudden jumps in brightness in certain intervals).
|
||||
|
||||
// ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31
|
||||
static uint32_t bright_scale[32] = {
|
||||
0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097,
|
||||
252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912,
|
||||
126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986,
|
||||
84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280};
|
||||
|
||||
auto max3 = [](u16 a, u16 b, u16 c) { return fl_max(fl_max(a, b), c); };
|
||||
|
||||
|
||||
if (brightness == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
}
|
||||
if (r16 == 0 && g16 == 0 && b16 == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = (brightness <= 31) ? brightness : 31;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t r8 = 0, g8 = 0, b8 = 0;
|
||||
|
||||
// Apply any brightness setting (we assume brightness is 0..255)
|
||||
if (brightness != 0xff) {
|
||||
r16 = scale16by8(r16, brightness);
|
||||
g16 = scale16by8(g16, brightness);
|
||||
b16 = scale16by8(b16, brightness);
|
||||
}
|
||||
|
||||
// Locate the largest value to set the brightness/scale factor
|
||||
uint16_t scale = max3(r16, g16, b16);
|
||||
|
||||
if (scale == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
} else {
|
||||
uint32_t scalef;
|
||||
|
||||
// Compute 5 bit quantized scale that is at or above the maximum value.
|
||||
scale = (scale + (2047 - (scale >> 5))) >> 11;
|
||||
|
||||
// Adjust the 16 bit values to account for the scale, then round to 8
|
||||
// bits
|
||||
scalef = bright_scale[scale];
|
||||
r8 = (r16 * scalef + 0x808000) >> 24;
|
||||
g8 = (g16 * scalef + 0x808000) >> 24;
|
||||
b8 = (b16 * scalef + 0x808000) >> 24;
|
||||
|
||||
*out = CRGB(r8, g8, b8);
|
||||
*out_power_5bit = static_cast<uint8_t>(scale);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
7
libraries/FastLED/src/fl/force_inline.h
Normal file
7
libraries/FastLED/src/fl/force_inline.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FASTLED_NO_FORCE_INLINE
|
||||
#define FASTLED_FORCE_INLINE inline
|
||||
#else
|
||||
#define FASTLED_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user