Initial commit of Arduino libraries

This commit is contained in:
Sam
2025-05-23 10:47:41 +10:00
commit 5bfce5fc3e
2476 changed files with 1108481 additions and 0 deletions

125
FastLED/src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,125 @@
# -----------------------------------------------------------------------------
# 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 (e.g. tests/CMakeLists.txt)
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})
# === Step 1: 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.*")
# -----------------------------------------------------------------------------
# Partition helper: create object libraries and add their names to a global list
# -----------------------------------------------------------------------------
# Initialize a global list to store the names of the created object libraries.
set(FASTLED_OBJECT_LIBS "")
# Using a macro (instead of a function) ensures we are in the global scope.
macro(add_partitioned_library lib_name source_regex)
set(matched_sources "")
foreach(source IN LISTS FASTLED_SOURCES)
if(source MATCHES "${source_regex}")
list(APPEND matched_sources "${source}")
endif()
endforeach()
if(matched_sources)
# Remove the matched sources from the global list so they won't be processed again.
foreach(source IN LISTS matched_sources)
list(REMOVE_ITEM FASTLED_SOURCES "${source}")
endforeach()
# Create an object library from the matched sources.
add_library(${lib_name} OBJECT ${matched_sources})
target_compile_options(${lib_name} PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_definitions(${lib_name} PRIVATE ${COMMON_COMPILE_DEFINITIONS})
message(STATUS "Created ${lib_name} with sources: ${matched_sources}")
# Append the library name to the global list.
list(APPEND FASTLED_OBJECT_LIBS ${lib_name})
else()
message(WARNING "No sources found for library ${lib_name} using filter: ${source_regex}")
endif()
endmacro()
# -----------------------------------------------------------------------------
# Step 2: Partition the sources into libraries with one-line calls.
# -----------------------------------------------------------------------------
# Partition some of the sources into separate object libraries, removing them from the global list FASTLED_SOURCES.
add_partitioned_library(FastLED_fx ".*/fx/")
add_partitioned_library(FastLED_platforms ".*/platforms/")
# Uncomment the next line if you wish to partition lib8tion sources:
# add_partitioned_library(fastled8tion ".*/lib8tion/")
add_partitioned_library(FastLED_fl ".*/fl/")
add_partitioned_library(FastLED_sensors ".*/sensors/")
add_partitioned_library(FastLED_third_party ".*/third_party/")
# Anything else will be considered the "core" library
# The remaining files become the "core" library.
if(FASTLED_SOURCES)
add_library(FastLED_core OBJECT ${FASTLED_SOURCES})
target_compile_options(FastLED_core PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_definitions(FastLED_core PRIVATE ${COMMON_COMPILE_DEFINITIONS})
list(APPEND FASTLED_OBJECT_LIBS FastLED_core)
else()
message(FATAL_ERROR "Error: FastLED_core has no sources! This should not happen.")
endif()
# -----------------------------------------------------------------------------
# Create the main FastLED library by aggregating all object libraries.
# -----------------------------------------------------------------------------
add_library(fastled STATIC)
# Loop over the global list of object libraries and add their object files.
foreach(obj_lib IN LISTS FASTLED_OBJECT_LIBS)
target_sources(fastled PRIVATE $<TARGET_OBJECTS:${obj_lib}>)
endforeach()
# Forward the compile flags and definitions from the parent.
target_compile_options(fastled PRIVATE ${COMMON_COMPILE_FLAGS})
target_compile_definitions(fastled PRIVATE ${COMMON_COMPILE_DEFINITIONS})
# Ensure full archive linking: force inclusion of all object files.
target_link_options(fastled PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive")

393
FastLED/src/FastLED.cpp Normal file
View File

@@ -0,0 +1,393 @@
#define FASTLED_INTERNAL
#include "FastLED.h"
#include "fl/singleton.h"
#include "fl/engine_events.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
#if defined(__SAM3X8E__)
volatile uint32_t 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
extern "C" __attribute__((weak)) int atexit(void (* /*func*/ )()) { return 0; }
#ifdef FASTLED_NEEDS_YIELD
extern "C" void yield(void) { }
#endif
FASTLED_NAMESPACE_BEGIN
uint16_t 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;
CFastLED FastLED;
CLEDController *CLEDController::m_pHead = NULL;
CLEDController *CLEDController::m_pTail = NULL;
static uint32_t lastshow = 0;
/// Global frame counter, used for debugging ESP implementations
/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block?
uint32_t _frame_cnt=0;
/// Global frame retry counter, used for debugging ESP implementations
/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block?
uint32_t _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;
}
static void* gControllersData[MAX_CLED_CONTROLLERS];
void CFastLED::show(uint8_t scale) {
fl::EngineEvents::onBeginFrame();
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);
}
// static uninitialized gControllersData produces the smallest binary on attiny85.
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();
fl::EngineEvents::onEndShowLeds();
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();
}
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 uint32_t lastframe = 0; // millis();
if(br++ >= nFrames) {
uint32_t 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(uint16_t 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 *) __attribute__((weak));
extern "C" void __cxa_guard_release (__guard *) __attribute__((weak));
extern "C" void __cxa_guard_abort (__guard *) __attribute__((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

863
FastLED/src/FastLED.h Normal file
View File

@@ -0,0 +1,863 @@
#pragma once
#ifndef __INC_FASTSPI_LED2_H
#define __INC_FASTSPI_LED2_H
#include <stdint.h>
/// @file FastLED.h
/// central include file for FastLED, defines the CFastLED class/object
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)
#define FASTLED_HAS_PRAGMA_MESSAGE
#endif
/// Current FastLED version number, as an integer.
/// E.g. 3007001 for version "3.7.1", with:
/// * 1 digit for the major version
/// * 3 digits for the minor version
/// * 3 digits for the patch version
#define FASTLED_VERSION 3009016
#ifndef FASTLED_INTERNAL
# ifdef FASTLED_SHOW_VERSION
# ifdef FASTLED_HAS_PRAGMA_MESSAGE
# pragma message "FastLED version 3.009.016"
# else
# warning FastLED version 3.009.016 (Not really a warning, just telling you here.)
# endif
# endif
#endif
#ifndef __PROG_TYPES_COMPAT__
/// avr-libc define to expose __progmem__ typedefs.
/// @note These typedefs are now deprecated!
/// @see https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html
#define __PROG_TYPES_COMPAT__
#endif
#ifdef SmartMatrix_h
#include <SmartMatrix.h>
#endif
#ifdef DmxSimple_h
#include <DmxSimple.h>
#endif
#ifdef DmxSerial_h
#include <DMXSerial.h>
#endif
#ifdef USE_OCTOWS2811
#include <OctoWS2811.h>
#endif
#include "fl/force_inline.h"
#include "cpp_compat.h"
#include "fastled_config.h"
#include "led_sysdefs.h"
// Utility functions
#include "fastled_delay.h"
#include "bitswap.h"
#include "controller.h"
#include "fastpin.h"
#include "fastspi_types.h"
#include "dmx.h"
#include "platforms.h"
#include "fastled_progmem.h"
#include "lib8tion.h"
#include "pixeltypes.h"
#include "hsv2rgb.h"
#include "colorutils.h"
#include "pixelset.h"
#include "colorpalettes.h"
#include "noise.h"
#include "power_mgt.h"
#include "fastspi.h"
#include "chipsets.h"
#include "fl/engine_events.h"
FASTLED_NAMESPACE_BEGIN
// Backdoor to get the size of the CLedController object. The one place
// that includes this just uses extern to declare the function.
uint16_t cled_contoller_size();
/// LED chipsets with SPI interface
enum ESPIChipsets {
LPD6803, ///< LPD6803 LED chipset
LPD8806, ///< LPD8806 LED chipset
WS2801, ///< WS2801 LED chipset
WS2803, ///< WS2803 LED chipset
SM16716, ///< SM16716 LED chipset
P9813, ///< P9813 LED chipset
APA102, ///< APA102 LED chipset
SK9822, ///< SK9822 LED chipset
SK9822HD, ///< SK9822 LED chipset with 5-bit gamma correction
DOTSTAR, ///< APA102 LED chipset alias
DOTSTARHD, ///< APA102HD LED chipset alias
APA102HD, ///< APA102 LED chipset with 5-bit gamma correction
HD107, /// Same as APA102, but in turbo 40-mhz mode.
HD107HD, /// Same as APA102HD, but in turbo 40-mhz mode.
};
/// Smart Matrix Library controller type
/// @see https://github.com/pixelmatix/SmartMatrix
enum ESM { SMART_MATRIX };
/// Octo WS2811 LED Library controller types
/// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html
/// @see https://github.com/PaulStoffregen/OctoWS2811
enum OWS2811 { OCTOWS2811,OCTOWS2811_400, OCTOWS2813};
/// WS2812Serial Library controller type
/// @see https://www.pjrc.com/non-blocking-ws2812-led-library/
/// @see https://github.com/PaulStoffregen/WS2812Serial
enum SWS2812 { WS2812SERIAL };
#ifdef HAS_PIXIE
template<uint8_t DATA_PIN, EOrder RGB_ORDER> class PIXIE : public PixieController<DATA_PIN, RGB_ORDER> {};
#endif
#ifdef FASTLED_HAS_CLOCKLESS
/// @addtogroup Chipsets
/// @{
/// @addtogroup ClocklessChipsets
/// @{
/// LED controller for WS2812 LEDs with GRB color order
/// @see WS2812Controller800Khz
template<uint8_t DATA_PIN> class NEOPIXEL : public WS2812Controller800Khz<DATA_PIN, GRB> {};
/// @brief SM16703 controller class.
/// @copydetails SM16703Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class SM16703 : public SM16703Controller<DATA_PIN, RGB_ORDER> {};
/// @brief TM1829 controller class.
/// @copydetails TM1829Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class TM1829 : public TM1829Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief TM1812 controller class.
/// @copydetails TM1809Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class TM1812 : public TM1809Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief TM1809 controller class.
/// @copydetails TM1809Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class TM1809 : public TM1809Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief TM1804 controller class.
/// @copydetails TM1809Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class TM1804 : public TM1809Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief TM1803 controller class.
/// @copydetails TM1803Controller400Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class TM1803 : public TM1803Controller400Khz<DATA_PIN, RGB_ORDER> {};
/// @brief UCS1903 controller class.
/// @copydetails UCS1903Controller400Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class UCS1903 : public UCS1903Controller400Khz<DATA_PIN, RGB_ORDER> {};
/// @brief UCS1903B controller class.
/// @copydetails UCS1903BController800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class UCS1903B : public UCS1903BController800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief UCS1904 controller class.
/// @copydetails UCS1904Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class UCS1904 : public UCS1904Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief UCS2903 controller class.
/// @copydetails UCS2903Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class UCS2903 : public UCS2903Controller<DATA_PIN, RGB_ORDER> {};
/// @brief WS2812 controller class.
/// @copydetails WS2812Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2812 : public WS2812Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief WS2815 controller class.
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2815 : public WS2815Controller<DATA_PIN, RGB_ORDER> {};
/// @brief WS2816 controller class.
template <uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2816 : public WS2816Controller<DATA_PIN, RGB_ORDER> {};
/// @brief WS2852 controller class.
/// @copydetails WS2812Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2852 : public WS2812Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief WS2812B controller class.
/// @copydetails WS2812Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2812B : public WS2812Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief GS1903 controller class.
/// @copydetails WS2812Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class GS1903 : public WS2812Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief SK6812 controller class.
/// @copydetails SK6812Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class SK6812 : public SK6812Controller<DATA_PIN, RGB_ORDER> {};
/// @brief SK6822 controller class.
/// @copydetails SK6822Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class SK6822 : public SK6822Controller<DATA_PIN, RGB_ORDER> {};
/// @brief APA106 controller class.
/// @copydetails SK6822Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class APA106 : public SK6822Controller<DATA_PIN, RGB_ORDER> {};
/// @brief PL9823 controller class.
/// @copydetails PL9823Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class PL9823 : public PL9823Controller<DATA_PIN, RGB_ORDER> {};
/// @brief WS2811 controller class.
/// @copydetails WS2811Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2811 : public WS2811Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief WS2813 controller class.
/// @copydetails WS2813Controller
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2813 : public WS2813Controller<DATA_PIN, RGB_ORDER> {};
/// @brief APA104 controller class.
/// @copydetails WS2811Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class APA104 : public WS2811Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief WS2811_400 controller class.
/// @copydetails WS2811Controller400Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class WS2811_400 : public WS2811Controller400Khz<DATA_PIN, RGB_ORDER> {};
/// @brief GE8822 controller class.
/// @copydetails GE8822Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class GE8822 : public GE8822Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief GW6205 controller class.
/// @copydetails GW6205Controller800Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class GW6205 : public GW6205Controller800Khz<DATA_PIN, RGB_ORDER> {};
/// @brief GW6205_400 controller class.
/// @copydetails GW6205Controller400Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class GW6205_400 : public GW6205Controller400Khz<DATA_PIN, RGB_ORDER> {};
/// @brief LPD1886 controller class.
/// @copydetails LPD1886Controller1250Khz
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class LPD1886 : public LPD1886Controller1250Khz<DATA_PIN, RGB_ORDER> {};
/// @brief LPD1886_8BIT controller class.
/// @copydetails LPD1886Controller1250Khz_8bit
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class LPD1886_8BIT : public LPD1886Controller1250Khz_8bit<DATA_PIN, RGB_ORDER> {};
/// @brief UCS1912 controller class.
template<uint8_t DATA_PIN, EOrder RGB_ORDER>
class UCS1912 : public UCS1912Controller<DATA_PIN, RGB_ORDER> {};
#if defined(DmxSimple_h) || defined(FASTLED_DOXYGEN)
/// @copydoc DMXSimpleController
template<uint8_t DATA_PIN, EOrder RGB_ORDER> class DMXSIMPLE : public DMXSimpleController<DATA_PIN, RGB_ORDER> {};
#endif
#if defined(DmxSerial_h) || defined(FASTLED_DOXYGEN)
/// @copydoc DMXSerialController
template<EOrder RGB_ORDER> class DMXSERIAL : public DMXSerialController<RGB_ORDER> {};
#endif
#endif
/// @} ClocklessChipsets
/// @} Chipsets
/// Blockless output port enum
enum EBlockChipsets {
#ifdef PORTA_FIRST_PIN
WS2811_PORTA,
WS2813_PORTA,
WS2811_400_PORTA,
TM1803_PORTA,
UCS1903_PORTA,
#endif
#ifdef PORTB_FIRST_PIN
WS2811_PORTB,
WS2813_PORTB,
WS2811_400_PORTB,
TM1803_PORTB,
UCS1903_PORTB,
#endif
#ifdef PORTC_FIRST_PIN
WS2811_PORTC,
WS2813_PORTC,
WS2811_400_PORTC,
TM1803_PORTC,
UCS1903_PORTC,
#endif
#ifdef PORTD_FIRST_PIN
WS2811_PORTD,
WS2813_PORTD,
WS2811_400_PORTD,
TM1803_PORTD,
UCS1903_PORTD,
#endif
#ifdef HAS_PORTDC
WS2811_PORTDC,
WS2813_PORTDC,
WS2811_400_PORTDC,
TM1803_PORTDC,
UCS1903_PORTDC,
#endif
};
/// Typedef for a power consumption calculation function. Used within
/// CFastLED for rescaling brightness before sending the LED data to
/// the strip with CFastLED::show().
/// @param scale the initial brightness scale value
/// @param data max power data, in milliwatts
/// @returns the brightness scale, limited to max power
typedef uint8_t (*power_func)(uint8_t scale, uint32_t data);
/// High level controller interface for FastLED.
/// This class manages controllers, global settings, and trackings such as brightness
/// and refresh rates, and provides access functions for driving led data to controllers
/// via the show() / showColor() / clear() methods.
/// This is instantiated as a global object with the name FastLED.
/// @nosubgrouping
class CFastLED {
// int m_nControllers;
uint8_t m_Scale; ///< the current global brightness scale setting
uint16_t m_nFPS; ///< tracking for current frames per second (FPS) value
uint32_t m_nMinMicros; ///< minimum µs between frames, used for capping frame rates
uint32_t m_nPowerData; ///< max power use parameter
power_func m_pPowerFunc; ///< function for overriding brightness when using FastLED.show();
public:
CFastLED();
// Useful when you want to know when an event like onFrameBegin or onFrameEnd is happening.
// This is disabled on AVR to save space.
void addListener(fl::EngineEvents::Listener *listener) { fl::EngineEvents::addListener(listener); }
void removeListener(fl::EngineEvents::Listener *listener) { fl::EngineEvents::removeListener(listener); }
/// Add a CLEDController instance to the world. Exposed to the public to allow people to implement their own
/// CLEDController objects or instances. There are two ways to call this method (as well as the other addLeds()
/// variations). The first is with 3 arguments, in which case the arguments are the controller, a pointer to
/// led data, and the number of leds used by this controller. The second is with 4 arguments, in which case
/// the first two arguments are the same, the third argument is an offset into the CRGB data where this controller's
/// CRGB data begins, and the fourth argument is the number of leds for this controller object.
/// @param pLed the led controller being added
/// @param data base pointer to an array of CRGB data structures
/// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array
/// @param nLedsIfOffset number of leds (4 argument version)
/// @returns a reference to the added controller
static CLEDController &addLeds(CLEDController *pLed, struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0);
/// @name Adding SPI-based controllers
/// Add an SPI based CLEDController instance to the world.
///
/// There are two ways to call this method (as well as the other addLeds()
/// variations). The first is with 2 arguments, in which case the arguments are a pointer to
/// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case
/// the first argument is the same, the second argument is an offset into the CRGB data where this controller's
/// CRGB data begins, and the third argument is the number of leds for this controller object.
///
/// @param data base pointer to an array of CRGB data structures
/// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array
/// @param nLedsIfOffset number of leds (4 argument version)
/// @tparam CHIPSET the chipset type
/// @tparam DATA_PIN the optional data pin for the leds (if omitted, will default to the first hardware SPI MOSI pin)
/// @tparam CLOCK_PIN the optional clock pin for the leds (if omitted, will default to the first hardware SPI clock pin)
/// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in)
/// @tparam SPI_DATA_RATE the data rate to drive the SPI clock at, defined using DATA_RATE_MHZ or DATA_RATE_KHZ macros
/// @returns a reference to the added controller
/// @{
// Base template: Causes a compile-time error if an unsupported CHIPSET is used
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN>
struct ClockedChipsetHelper {
// Default implementation, will be specialized for supported chipsets
static const bool IS_VALID = false;
};
// Macro to define a mapping from the ESPIChipeset enum to the controller class
// in it's various template configurations.
#define _FL_MAP_CLOCKED_CHIPSET(CHIPSET_ENUM, CONTROLLER_CLASS) \
template<uint8_t DATA_PIN, uint8_t CLOCK_PIN> \
struct ClockedChipsetHelper<CHIPSET_ENUM, DATA_PIN, CLOCK_PIN> { \
static const bool IS_VALID = true; \
typedef CONTROLLER_CLASS<DATA_PIN, CLOCK_PIN> ControllerType; \
/* Controller type with RGB_ORDER specified */ \
template<EOrder RGB_ORDER> \
struct CONTROLLER_CLASS_WITH_ORDER { \
typedef CONTROLLER_CLASS<DATA_PIN, CLOCK_PIN, RGB_ORDER> ControllerType; \
}; \
/* Controller type with RGB_ORDER and spi frequency specified */ \
template<EOrder RGB_ORDER, uint32_t FREQ> \
struct CONTROLLER_CLASS_WITH_ORDER_AND_FREQ { \
typedef CONTROLLER_CLASS<DATA_PIN, CLOCK_PIN, RGB_ORDER, FREQ> ControllerType; \
}; \
};
// Define specializations for each supported CHIPSET
_FL_MAP_CLOCKED_CHIPSET(LPD6803, LPD6803Controller)
_FL_MAP_CLOCKED_CHIPSET(LPD8806, LPD8806Controller)
_FL_MAP_CLOCKED_CHIPSET(WS2801, WS2801Controller)
_FL_MAP_CLOCKED_CHIPSET(WS2803, WS2803Controller)
_FL_MAP_CLOCKED_CHIPSET(SM16716, SM16716Controller)
_FL_MAP_CLOCKED_CHIPSET(P9813, P9813Controller)
// Both DOTSTAR and APA102 use the same controller class
_FL_MAP_CLOCKED_CHIPSET(DOTSTAR, APA102Controller)
_FL_MAP_CLOCKED_CHIPSET(APA102, APA102Controller)
// Both DOTSTARHD and APA102HD use the same controller class
_FL_MAP_CLOCKED_CHIPSET(DOTSTARHD, APA102ControllerHD)
_FL_MAP_CLOCKED_CHIPSET(APA102HD, APA102ControllerHD)
_FL_MAP_CLOCKED_CHIPSET(HD107, APA102Controller)
_FL_MAP_CLOCKED_CHIPSET(HD107HD, APA102ControllerHD)
_FL_MAP_CLOCKED_CHIPSET(SK9822, SK9822Controller)
_FL_MAP_CLOCKED_CHIPSET(SK9822HD, SK9822ControllerHD)
/// Add an SPI based CLEDController instance to the world.
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint32_t SPI_DATA_RATE > CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
// Instantiate the controller using ClockedChipsetHelper
typedef ClockedChipsetHelper<CHIPSET, DATA_PIN, CLOCK_PIN> CHIP;
typedef typename CHIP::template CONTROLLER_CLASS_WITH_ORDER_AND_FREQ<RGB_ORDER, SPI_DATA_RATE>::ControllerType ControllerTypeWithFreq;
static_assert(CHIP::IS_VALID, "Unsupported chipset");
static ControllerTypeWithFreq c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
/// Add an SPI based CLEDController instance to the world.
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN > static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
typedef ClockedChipsetHelper<CHIPSET, DATA_PIN, CLOCK_PIN> CHIP;
typedef typename CHIP::ControllerType ControllerType;
static_assert(CHIP::IS_VALID, "Unsupported chipset");
static ControllerType c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
// The addLeds function using ChipsetHelper
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER>
CLEDController& addLeds(struct CRGB* data, int nLedsOrOffset, int nLedsIfOffset = 0) {
typedef ClockedChipsetHelper<CHIPSET, DATA_PIN, CLOCK_PIN> CHIP;
static_assert(CHIP::IS_VALID, "Unsupported chipset");
typedef typename CHIP::template CONTROLLER_CLASS_WITH_ORDER<RGB_ORDER>::ControllerType ControllerTypeWithOrder;
static ControllerTypeWithOrder c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
#ifdef SPI_DATA
template<ESPIChipsets CHIPSET> static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
return addLeds<CHIPSET, SPI_DATA, SPI_CLOCK, RGB>(data, nLedsOrOffset, nLedsIfOffset);
}
template<ESPIChipsets CHIPSET, EOrder RGB_ORDER> static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
return addLeds<CHIPSET, SPI_DATA, SPI_CLOCK, RGB_ORDER>(data, nLedsOrOffset, nLedsIfOffset);
}
template<ESPIChipsets CHIPSET, EOrder RGB_ORDER, uint32_t SPI_DATA_RATE> static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
return addLeds<CHIPSET, SPI_DATA, SPI_CLOCK, RGB_ORDER, SPI_DATA_RATE>(data, nLedsOrOffset, nLedsIfOffset);
}
#endif
/// @} Adding SPI based controllers
#ifdef FASTLED_HAS_CLOCKLESS
/// @name Adding 3-wire led controllers
/// Add a clockless (aka 3-wire, also DMX) based CLEDController instance to the world.
///
/// There are two ways to call this method (as well as the other addLeds()
/// variations). The first is with 2 arguments, in which case the arguments are a pointer to
/// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case
/// the first argument is the same, the second argument is an offset into the CRGB data where this controller's
/// CRGB data begins, and the third argument is the number of leds for this controller object.
///
/// This method also takes 2 to 3 template parameters for identifying the specific chipset, data pin,
/// RGB ordering, and SPI data rate
///
/// @param data base pointer to an array of CRGB data structures
/// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array
/// @param nLedsIfOffset number of leds (4 argument version)
/// @tparam CHIPSET the chipset type (required)
/// @tparam DATA_PIN the data pin for the leds (required)
/// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in)
/// @returns a reference to the added controller
/// @{
/// Add a clockless based CLEDController instance to the world.
template<template<uint8_t DATA_PIN, EOrder RGB_ORDER> class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<DATA_PIN, RGB_ORDER> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
/// Add a clockless based CLEDController instance to the world.
template<template<uint8_t DATA_PIN, EOrder RGB_ORDER> class CHIPSET, uint8_t DATA_PIN>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<DATA_PIN, RGB> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
/// Add a clockless based CLEDController instance to the world.
template<template<uint8_t DATA_PIN> class CHIPSET, uint8_t DATA_PIN>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<DATA_PIN> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
#if defined(__FASTLED_HAS_FIBCC) && (__FASTLED_HAS_FIBCC == 1)
template<uint8_t NUM_LANES, template<uint8_t DATA_PIN, EOrder RGB_ORDER> class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER=RGB>
static CLEDController &addLeds(struct CRGB *data, int nLeds) {
static __FIBCC<CHIPSET, DATA_PIN, NUM_LANES, RGB_ORDER> c;
return addLeds(&c, data, nLeds);
}
#endif
#ifdef FASTSPI_USE_DMX_SIMPLE
template<EClocklessChipsets CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER=RGB>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0)
{
switch(CHIPSET) {
case DMX: { static DMXController<DATA_PIN> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
}
}
#endif
/// @} Adding 3-wire led controllers
#endif
/// @name Adding 3rd party library controllers
/// Add a 3rd party library based CLEDController instance to the world.
///
/// There are two ways to call this method (as well as the other addLeds()
/// variations). The first is with 2 arguments, in which case the arguments are a pointer to
/// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case
/// the first argument is the same, the second argument is an offset into the CRGB data where this controller's
/// CRGB data begins, and the third argument is the number of leds for this controller object. This class includes the SmartMatrix
/// and OctoWS2811 based controllers
///
/// This method also takes 1 to 2 template parameters for identifying the specific chipset and
/// RGB ordering.
///
/// @param data base pointer to an array of CRGB data structures
/// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array
/// @param nLedsIfOffset number of leds (4 argument version)
/// @tparam CHIPSET the chipset type (required)
/// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in)
/// @returns a reference to the added controller
/// @{
/// Add a 3rd party library based CLEDController instance to the world.
template<template<EOrder RGB_ORDER> class CHIPSET, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<RGB_ORDER> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
/// Add a 3rd party library based CLEDController instance to the world.
template<template<EOrder RGB_ORDER> class CHIPSET>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<RGB> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
#ifdef USE_OCTOWS2811
/// Add a OCTOWS2811 based CLEDController instance to the world.
/// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html
/// @see https://github.com/PaulStoffregen/OctoWS2811
template<OWS2811 CHIPSET, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0)
{
switch(CHIPSET) {
case OCTOWS2811: { static COctoWS2811Controller<RGB_ORDER,WS2811_800kHz> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
case OCTOWS2811_400: { static COctoWS2811Controller<RGB_ORDER,WS2811_400kHz> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
#ifdef WS2813_800kHz
case OCTOWS2813: { static COctoWS2811Controller<RGB_ORDER,WS2813_800kHz> controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
#endif
}
}
/// Add a OCTOWS2811 library based CLEDController instance to the world.
/// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html
/// @see https://github.com/PaulStoffregen/OctoWS2811
template<OWS2811 CHIPSET>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0)
{
return addLeds<CHIPSET,GRB>(data,nLedsOrOffset,nLedsIfOffset);
}
#endif
#ifdef USE_WS2812SERIAL
/// Add a WS2812Serial library based CLEDController instance to the world.
/// @see https://www.pjrc.com/non-blocking-ws2812-led-library/
/// @see https://github.com/PaulStoffregen/WS2812Serial
template<SWS2812 CHIPSET, int DATA_PIN, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0)
{
static CWS2812SerialController<DATA_PIN,RGB_ORDER> controller;
return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset);
}
#endif
#ifdef SmartMatrix_h
/// Add a SmartMatrix library based CLEDController instance to the world.
/// @see https://github.com/pixelmatix/SmartMatrix
template<ESM CHIPSET>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0)
{
switch(CHIPSET) {
case SMART_MATRIX: { static CSmartMatrixController controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); }
}
}
#endif
/// @} Adding 3rd party library controllers
#ifdef FASTLED_HAS_BLOCKLESS
/// @name Adding parallel output controllers
/// Add a block based CLEDController instance to the world.
///
/// There are two ways to call this method (as well as the other addLeds()
/// variations). The first is with 2 arguments, in which case the arguments are a pointer to
/// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case
/// the first argument is the same, the second argument is an offset into the CRGB data where this controller's
/// CRGB data begins, and the third argument is the number of leds for this controller object.
///
/// This method also takes a 2 to 3 template parameters for identifying the specific chipset and rgb ordering
/// RGB ordering, and SPI data rate
///
/// @param data base pointer to an array of CRGB data structures
/// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array
/// @param nLedsIfOffset number of leds (4 argument version)
/// @tparam CHIPSET the chipset/port type (required)
/// @tparam NUM_LANES how many parallel lanes of output to write
/// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in)
/// @returns a reference to the added controller
/// @{
/// Add a block based parallel output CLEDController instance to the world.
template<EBlockChipsets CHIPSET, int NUM_LANES, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
switch(CHIPSET) {
#ifdef PORTA_FIRST_PIN
case WS2811_PORTA: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTA_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2811_400_PORTA: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTA_FIRST_PIN, NS(800), NS(800), NS(900), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2813_PORTA: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTA_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER, 0, false, 300>(), data, nLedsOrOffset, nLedsIfOffset);
case TM1803_PORTA: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTA_FIRST_PIN, NS(700), NS(1100), NS(700), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case UCS1903_PORTA: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTA_FIRST_PIN, NS(500), NS(1500), NS(500), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
#endif
#ifdef PORTB_FIRST_PIN
case WS2811_PORTB: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTB_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2811_400_PORTB: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTB_FIRST_PIN, NS(800), NS(800), NS(900), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2813_PORTB: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTB_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER, 0, false, 300>(), data, nLedsOrOffset, nLedsIfOffset);
case TM1803_PORTB: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTB_FIRST_PIN, NS(700), NS(1100), NS(700), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case UCS1903_PORTB: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTB_FIRST_PIN, NS(500), NS(1500), NS(500), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
#endif
#ifdef PORTC_FIRST_PIN
case WS2811_PORTC: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTC_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2811_400_PORTC: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTC_FIRST_PIN, NS(800), NS(800), NS(900), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2813_PORTC: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTC_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER, 0, false, 300>(), data, nLedsOrOffset, nLedsIfOffset);
case TM1803_PORTC: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTC_FIRST_PIN, NS(700), NS(1100), NS(700), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case UCS1903_PORTC: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTC_FIRST_PIN, NS(500), NS(1500), NS(500), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
#endif
#ifdef PORTD_FIRST_PIN
case WS2811_PORTD: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTD_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2811_400_PORTD: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTD_FIRST_PIN, NS(800), NS(800), NS(900), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2813_PORTD: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTD_FIRST_PIN, NS(320), NS(320), NS(640), RGB_ORDER, 0, false, 300>(), data, nLedsOrOffset, nLedsIfOffset);
case TM1803_PORTD: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTD_FIRST_PIN, NS(700), NS(1100), NS(700), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case UCS1903_PORTD: return addLeds(new InlineBlockClocklessController<NUM_LANES, PORTD_FIRST_PIN, NS(500), NS(1500), NS(500), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
#endif
#ifdef HAS_PORTDC
case WS2811_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController<NUM_LANES,NS(320), NS(320), NS(640), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2811_400_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController<NUM_LANES,NS(800), NS(800), NS(900), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case WS2813_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController<NUM_LANES, NS(320), NS(320), NS(640), RGB_ORDER, 0, false, 300>(), data, nLedsOrOffset, nLedsIfOffset);
case TM1803_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController<NUM_LANES, NS(700), NS(1100), NS(700), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
case UCS1903_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController<NUM_LANES, NS(500), NS(1500), NS(500), RGB_ORDER>(), data, nLedsOrOffset, nLedsIfOffset);
#endif
}
}
/// Add a block based parallel output CLEDController instance to the world.
template<EBlockChipsets CHIPSET, int NUM_LANES>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
return addLeds<CHIPSET,NUM_LANES,GRB>(data,nLedsOrOffset,nLedsIfOffset);
}
/// @} Adding parallel output controllers
#endif
/// Set the global brightness scaling
/// @param scale a 0-255 value for how much to scale all leds before writing them out
void setBrightness(uint8_t scale) { m_Scale = scale; }
/// Get the current global brightness setting
/// @returns the current global brightness value
uint8_t getBrightness() { return m_Scale; }
/// Set the maximum power to be used, given in volts and milliamps.
/// @param volts how many volts the leds are being driven at (usually 5)
/// @param milliamps the maximum milliamps of power draw you want
inline void setMaxPowerInVoltsAndMilliamps(uint8_t volts, uint32_t milliamps) { setMaxPowerInMilliWatts(volts * milliamps); }
/// Set the maximum power to be used, given in milliwatts
/// @param milliwatts the max power draw desired, in milliwatts
inline void setMaxPowerInMilliWatts(uint32_t milliwatts) { m_pPowerFunc = &calculate_max_brightness_for_power_mW; m_nPowerData = milliwatts; }
/// Update all our controllers with the current led colors, using the passed in brightness
/// @param scale the brightness value to use in place of the stored value
void show(uint8_t scale);
/// Update all our controllers with the current led colors
void show() { show(m_Scale); }
/// Clear the leds, wiping the local array of data. Optionally you can also
/// send the cleared data to the LEDs.
/// @param writeData whether or not to write out to the leds as well
void clear(bool writeData = false);
/// Clear out the local data array
void clearData();
/// Set all leds on all controllers to the given color/scale.
/// @param color what color to set the leds to
/// @param scale what brightness scale to show at
void showColor(const struct CRGB & color, uint8_t scale);
/// Set all leds on all controllers to the given color
/// @param color what color to set the leds to
void showColor(const struct CRGB & color) { showColor(color, m_Scale); }
/// Delay for the given number of milliseconds. Provided to allow the library to be used on platforms
/// that don't have a delay function (to allow code to be more portable).
/// @note This will call show() constantly to drive the dithering engine (and will call show() at least once).
/// @param ms the number of milliseconds to pause for
void delay(unsigned long ms);
/// Set a global color temperature. Sets the color temperature for all added led strips,
/// overriding whatever previous color temperature those controllers may have had.
/// @param temp A CRGB structure describing the color temperature
void setTemperature(const struct CRGB & temp);
/// Set a global color correction. Sets the color correction for all added led strips,
/// overriding whatever previous color correction those controllers may have had.
/// @param correction A CRGB structure describin the color correction.
void setCorrection(const struct CRGB & correction);
/// Set the dithering mode. Sets the dithering mode for all added led strips, overriding
/// whatever previous dithering option those controllers may have had.
/// @param ditherMode what type of dithering to use, either BINARY_DITHER or DISABLE_DITHER
void setDither(uint8_t ditherMode = BINARY_DITHER);
/// Set the maximum refresh rate. This is global for all leds. Attempts to
/// call show() faster than this rate will simply wait.
/// @note The refresh rate defaults to the slowest refresh rate of all the leds added through addLeds().
/// If you wish to set/override this rate, be sure to call setMaxRefreshRate() _after_
/// adding all of your leds.
/// @param refresh maximum refresh rate in hz
/// @param constrain constrain refresh rate to the slowest speed yet set
void setMaxRefreshRate(uint16_t refresh, bool constrain=false);
/// For debugging, this will keep track of time between calls to countFPS(). Every
/// `nFrames` calls, it will update an internal counter for the current FPS.
/// @todo Make this a rolling counter
/// @param nFrames how many frames to time for determining FPS
void countFPS(int nFrames=25);
/// Get the number of frames/second being written out
/// @returns the most recently computed FPS value
uint16_t getFPS() { return m_nFPS; }
/// Get how many controllers have been registered
/// @returns the number of controllers (strips) that have been added with addLeds()
int count();
/// Get a reference to a registered controller
/// @returns a reference to the Nth controller
CLEDController & operator[](int x);
/// Get the number of leds in the first controller
/// @returns the number of LEDs in the first controller
int size();
/// Get a pointer to led data for the first controller
/// @returns pointer to the CRGB buffer for the first controller
CRGB *leds();
};
/// Alias of the FastLED instance for legacy purposes
#define FastSPI_LED FastLED
/// Alias of the FastLED instance for legacy purposes
#define FastSPI_LED2 FastLED
#ifndef LEDS
/// Alias of the FastLED instance for legacy purposes
#define LEDS FastLED
#endif
/// Global LED strip management instance
extern CFastLED FastLED;
/// If no pin/port mappings are found, sends a warning message to the user
/// during compilation.
/// @see fastpin.h
#ifndef HAS_HARDWARE_PIN_SUPPORT
#warning "No pin/port mappings found, pin access will be slightly slower. See fastpin.h for info."
#define NO_HARDWARE_PIN_SUPPORT
#endif
FASTLED_NAMESPACE_END
#endif
#ifdef FASTLED_UI
// As a convenience, include the UI headers and bring them into the global namespace
#include "fl/ui.h"
#include "fl/xymap.h"
using fl::UIButton;
using fl::UICheckbox;
using fl::UINumberField;
using fl::UISlider;
using fl::XYMap;
#define FASTLED_TITLE(text) fl::UITitle g_title(text)
#define FASTLED_DESCRIPTION(text) fl::UIDescription g_description(text)
#endif // FASTLED_UI
#if defined(FASTLED_FORCE_USE_NAMESPACE) && FASTLED_FORCE_USE_NAMESPACE==1
using namespace fl;
#endif

5
FastLED/src/allocator.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#warning "This header is deprecated. Please use allocator.h, this header will go away in the version 4.0."
#include "fl/allocator.h"

5
FastLED/src/bitswap.cpp Normal file
View 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

293
FastLED/src/bitswap.h Normal file
View File

@@ -0,0 +1,293 @@
#ifndef __INC_BITSWAP_H
#define __INC_BITSWAP_H
#include "FastLED.h"
#include "fl/force_inline.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 {
uint8_t raw; ///< the entire byte
struct {
uint32_t a0:1; ///< bit 0 (0x01)
uint32_t a1:1; ///< bit 1 (0x02)
uint32_t a2:1; ///< bit 2 (0x04)
uint32_t a3:1; ///< bit 3 (0x08)
uint32_t a4:1; ///< bit 4 (0x10)
uint32_t a5:1; ///< bit 5 (0x20)
uint32_t a6:1; ///< bit 6 (0x40)
uint32_t a7:1; ///< bit 7 (0x80)
};
} just8bits;
/// Structure representing 32 bits of access
typedef struct {
uint32_t a0:1; ///< byte 'a', bit 0 (0x00000000)
uint32_t a1:1; ///< byte 'a', bit 1 (0x00000002)
uint32_t a2:1; ///< byte 'a', bit 2 (0x00000004)
uint32_t a3:1; ///< byte 'a', bit 3 (0x00000008)
uint32_t a4:1; ///< byte 'a', bit 4 (0x00000010)
uint32_t a5:1; ///< byte 'a', bit 5 (0x00000020)
uint32_t a6:1; ///< byte 'a', bit 6 (0x00000040)
uint32_t a7:1; ///< byte 'a', bit 7 (0x00000080)
uint32_t b0:1; ///< byte 'b', bit 0 (0x00000100)
uint32_t b1:1; ///< byte 'b', bit 1 (0x00000200)
uint32_t b2:1; ///< byte 'b', bit 2 (0x00000400)
uint32_t b3:1; ///< byte 'b', bit 3 (0x00000800)
uint32_t b4:1; ///< byte 'b', bit 4 (0x00001000)
uint32_t b5:1; ///< byte 'b', bit 5 (0x00002000)
uint32_t b6:1; ///< byte 'b', bit 6 (0x00004000)
uint32_t b7:1; ///< byte 'b', bit 7 (0x00008000)
uint32_t c0:1; ///< byte 'c', bit 0 (0x00010000)
uint32_t c1:1; ///< byte 'c', bit 1 (0x00020000)
uint32_t c2:1; ///< byte 'c', bit 2 (0x00040000)
uint32_t c3:1; ///< byte 'c', bit 3 (0x00080000)
uint32_t c4:1; ///< byte 'c', bit 4 (0x00100000)
uint32_t c5:1; ///< byte 'c', bit 5 (0x00200000)
uint32_t c6:1; ///< byte 'c', bit 6 (0x00400000)
uint32_t c7:1; ///< byte 'c', bit 7 (0x00800000)
uint32_t d0:1; ///< byte 'd', bit 0 (0x01000000)
uint32_t d1:1; ///< byte 'd', bit 1 (0x02000000)
uint32_t d2:1; ///< byte 'd', bit 2 (0x04000000)
uint32_t d3:1; ///< byte 'd', bit 3 (0x08000000)
uint32_t d4:1; ///< byte 'd', bit 4 (0x10000000)
uint32_t d5:1; ///< byte 'd', bit 5 (0x20000000)
uint32_t d6:1; ///< byte 'd', bit 6 (0x40000000)
uint32_t d7:1; ///< byte 'd', bit 7 (0x80000000)
} sub4;
/// Union containing a full 8 bytes to swap the bit orientation on
typedef union {
uint32_t word[2]; ///< two 32-bit values to load for swapping
uint8_t 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) {
uint8_t x = A[row];
uint8_t bit = (1<<row);
unsigned char *p = B;
for(uint32_t 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) {
uint32_t 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) {
uint32_t 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) {
uint32_t 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

1234
FastLED/src/chipsets.h Normal file

File diff suppressed because it is too large Load Diff

110
FastLED/src/chsv.h Normal file
View File

@@ -0,0 +1,110 @@
/// @file chsv.h
/// Defines the hue, saturation, and value (HSV) pixel struct
#pragma once
#include <stdint.h>
#include "fl/namespace.h"
FASTLED_NAMESPACE_BEGIN
/// @addtogroup PixelTypes Pixel Data Types (CRGB/CHSV)
/// @{
/// Representation of an HSV pixel (hue, saturation, value (aka brightness)).
struct CHSV {
union {
struct {
union {
/// Color hue.
/// This is an 8-bit value representing an angle around
/// the color wheel. Where 0 is 0°, and 255 is 358°.
uint8_t hue;
uint8_t h; ///< @copydoc hue
};
union {
/// Color saturation.
/// This is an 8-bit value representing a percentage.
uint8_t saturation;
uint8_t sat; ///< @copydoc saturation
uint8_t s; ///< @copydoc saturation
};
union {
/// Color value (brightness).
/// This is an 8-bit value representing a percentage.
uint8_t value;
uint8_t val; ///< @copydoc value
uint8_t v; ///< @copydoc value
};
};
/// Access the hue, saturation, and value data as an array.
/// Where:
/// * `raw[0]` is the hue
/// * `raw[1]` is the saturation
/// * `raw[2]` is the value
uint8_t raw[3];
};
/// Array access operator to index into the CHSV object
/// @param x the index to retrieve (0-2)
/// @returns the CHSV::raw value for the given index
inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline))
{
return raw[x];
}
/// @copydoc operator[]
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline))
{
return raw[x];
}
/// Default constructor
/// @warning Default values are UNITIALIZED!
constexpr inline CHSV() __attribute__((always_inline)): h(0), s(0), v(0) { }
/// Allow construction from hue, saturation, and value
/// @param ih input hue
/// @param is input saturation
/// @param iv input value
constexpr inline CHSV( uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline))
: h(ih), s(is), v(iv)
{
}
/// Allow copy construction
constexpr inline CHSV(const CHSV& rhs) noexcept : h(rhs.h), s(rhs.s), v(rhs.v) { }
/// Allow copy construction
inline CHSV& operator= (const CHSV& rhs) __attribute__((always_inline)) = default;
/// Assign new HSV values
/// @param ih input hue
/// @param is input saturation
/// @param iv input value
/// @returns reference to the CHSV object
inline CHSV& setHSV(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline))
{
h = ih;
s = is;
v = iv;
return *this;
}
};
/// Pre-defined hue values for CHSV objects
typedef enum {
HUE_RED = 0, ///< Red (0°)
HUE_ORANGE = 32, ///< Orange (45°)
HUE_YELLOW = 64, ///< Yellow (90°)
HUE_GREEN = 96, ///< Green (135°)
HUE_AQUA = 128, ///< Aqua (180°)
HUE_BLUE = 160, ///< Blue (225°)
HUE_PURPLE = 192, ///< Purple (270°)
HUE_PINK = 224 ///< Pink (315°)
} HSVHue;
/// @} PixelTypes
FASTLED_NAMESPACE_END

View File

@@ -0,0 +1,48 @@
/// @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"
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;
memset((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

View File

@@ -0,0 +1,279 @@
#pragma once
#include <stddef.h>
/// @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"
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, uint8_t 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, uint8_t 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(uint8_t brightness) {
void* data = beginShowLeds(m_nLeds);
showLedsInternal(brightness);
endShowLeds(data);
}
ColorAdjustment getAdjustmentData(uint8_t 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, uint8_t 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, uint8_t 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, uint8_t)
void showLedsInternal(uint8_t 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, uint8_t 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(uint8_t ditherMode = BINARY_DITHER) { m_DitherMode = ditherMode; return *this; }
CLEDController& setScreenMap(const fl::XYMap& map) {
// EngineEvents::onCanvasUiSet(this, map);
fl::ScreenMap screenmap = map.toScreenMap();
fl::EngineEvents::onCanvasUiSet(this, screenmap);
return *this;
}
CLEDController& setScreenMap(const fl::ScreenMap& map) {
fl::EngineEvents::onCanvasUiSet(this, map);
return *this;
}
CLEDController& setScreenMap(uint16_t width, uint16_t height) {
return setScreenMap(fl::XYMap::constructRectangularGrid(width, height));
}
/// Get the dithering option currently set for this controller
/// @return the currently set dithering option (CLEDController::m_DitherMode)
inline uint8_t 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 = reinterpret_cast<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 = reinterpret_cast<uintptr_t>(data);
setDither(static_cast<uint8_t>(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(uint8_t 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 uint16_t getMaxRefreshRate() const { return 0; }
};
FASTLED_NAMESPACE_END

100
FastLED/src/color.h Normal file
View 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

View 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
/// @}
/// @}

View File

@@ -0,0 +1,36 @@
#ifndef __INC_COLORPALETTES_H
#define __INC_COLORPALETTES_H
#include "FastLED.h"
#include "colorutils.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

1453
FastLED/src/colorutils.cpp Normal file

File diff suppressed because it is too large Load Diff

2308
FastLED/src/colorutils.h Normal file

File diff suppressed because it is too large Load Diff

9
FastLED/src/controller.h Normal file
View File

@@ -0,0 +1,9 @@
#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

View File

@@ -0,0 +1,74 @@
#pragma once
/// @file cpixel_ledcontroller.h
/// defines the templated version of the CLEDController class
#include <stddef.h>
#include "FastLED.h"
#include "led_sysdefs.h"
#include "pixeltypes.h"
#include "color.h"
#include "eorder.h"
#include "fl/force_inline.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, uint32_t 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, uint8_t 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, uint8_t 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 uint32_t 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

26
FastLED/src/cpp_compat.h Normal file
View File

@@ -0,0 +1,26 @@
/// @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

79
FastLED/src/crgb.cpp Normal file
View File

@@ -0,0 +1,79 @@
/// @file crgb.cpp
/// Utility functions for the red, green, and blue (RGB) pixel struct
#define FASTLED_INTERNAL
#include "FastLED.h"
#include "crgb.h"
#include "lib8tion/math8.h"
#include "fl/namespace.h"
FASTLED_NAMESPACE_BEGIN
fl::Str CRGB::toString() const {
fl::Str 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.
uint32_t work = (((uint16_t)cc)+1);
work *= (((uint16_t)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 * (255alpha) + upper * alpha) / 256
uint8_t amountOf2 = 255 - max_component;
return CRGB::blend(upper, lower, amountOf2);
}
CRGB& CRGB::nscale8 (uint8_t scaledown )
{
nscale8x3( r, g, b, scaledown);
return *this;
}
FASTLED_NAMESPACE_END

755
FastLED/src/crgb.h Normal file
View File

@@ -0,0 +1,755 @@
/// @file crgb.h
/// Defines the red, green, and blue (RGB) pixel struct
#pragma once
#include <stdint.h>
#include "chsv.h"
#include "fl/namespace.h"
#include "color.h"
#include "lib8tion/types.h"
#include "fl/force_inline.h"
#include "fl/template_magic.h"
namespace fl {
class Str;
}
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;
/// @defgroup PixelTypes Pixel Data Types (CRGB/CHSV)
/// @brief Structs that hold pixel color data
/// @{
/// Forward declaration of hsv2rgb_rainbow here,
/// to avoid circular dependencies.
///
/// Convert an HSV value to RGB using a visually balanced rainbow.
/// This "rainbow" yields better yellow and orange than a straight
/// mathematical "spectrum".
///
/// ![FastLED 'Rainbow' Hue Chart](https://raw.githubusercontent.com/FastLED/FastLED/gh-pages/images/HSV-rainbow-with-desc.jpg)
///
/// @param hsv CHSV struct to convert to RGB. Max hue supported is HUE_MAX_RAINBOW
/// @param rgb CRGB struct to store the result of the conversion (will be modified)
void hsv2rgb_rainbow( const struct CHSV& hsv, struct CRGB& rgb);
/// Representation of an RGB pixel (Red, Green, Blue)
struct CRGB {
union {
struct {
union {
uint8_t r; ///< Red channel value
uint8_t red; ///< @copydoc r
};
union {
uint8_t g; ///< Green channel value
uint8_t green; ///< @copydoc g
};
union {
uint8_t b; ///< Blue channel value
uint8_t 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
uint8_t raw[3];
};
static CRGB blend(const CRGB& p1, const CRGB& p2, fract8 amountOfP2);
static CRGB blendAlphaMaxChannel(const CRGB& upper, const CRGB& lower);
/// 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 uint8_t& operator[] (uint8_t 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 uint8_t& operator[] (uint8_t x) const
{
return raw[x];
}
/// Default constructor
/// @warning Default values are UNITIALIZED!
FASTLED_FORCE_INLINE CRGB() = default;
/// Allow construction from red, green, and blue
/// @param ir input red value
/// @param ig input green value
/// @param ib input blue value
constexpr CRGB(uint8_t ir, uint8_t ig, uint8_t 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(uint32_t colorcode) noexcept
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF)
{
}
constexpr uint32_t as_uint32_t() const noexcept {
return uint32_t(0xff000000) |
(uint32_t{r} << 16) |
(uint32_t{g} << 8) |
uint32_t{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_rainbow( rhs, *this);
}
/// 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 uint32_t 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 (uint8_t nr, uint8_t ng, uint8_t 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 (uint8_t hue, uint8_t sat, uint8_t val)
{
hsv2rgb_rainbow( 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 (uint8_t hue)
{
hsv2rgb_rainbow( CHSV(hue, 255, 255), *this);
return *this;
}
/// Allow assignment from HSV color
FASTLED_FORCE_INLINE CRGB& operator= (const CHSV& rhs)
{
hsv2rgb_rainbow( 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 (uint32_t 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
FASTLED_FORCE_INLINE 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 (uint8_t 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(uint8_t 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/= (uint8_t d )
{
r /= d;
g /= d;
b /= d;
return *this;
}
/// Right shift each of the channels by a constant
FASTLED_FORCE_INLINE CRGB& operator>>= (uint8_t d)
{
r >>= d;
g >>= d;
b >>= d;
return *this;
}
/// Multiply each of the channels by a constant,
/// saturating each channel at 0xFF.
FASTLED_FORCE_INLINE CRGB& operator*= (uint8_t 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 (uint8_t scaledown);
/// %= is a synonym for nscale8_video(). Think of it is scaling down
/// by "a percentage"
FASTLED_FORCE_INLINE CRGB& operator%= (uint8_t 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 (uint8_t 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 (uint8_t 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 (uint8_t 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)
FASTLED_FORCE_INLINE CRGB& fadeToBlackBy (uint8_t 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|= (uint8_t d )
{
if( d > r) r = d;
if( d > g) g = d;
if( d > b) b = d;
return *this;
}
/// "and" operator brings each channel down to the lower of the two values
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&= (uint8_t d )
{
if( d < r) r = d;
if( d < g) g = d;
if( d < b) b = d;
return *this;
}
/// This allows testing a CRGB for zero-ness
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 uint32_t() const
{
return uint32_t(0xff000000) |
(uint32_t{r} << 16) |
(uint32_t{g} << 8) |
uint32_t{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::Str 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).
uint8_t getLuma() const;
/// Get the average of the R, G, and B values
FASTLED_FORCE_INLINE uint8_t 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( uint8_t limit = 255 ) {
uint8_t max = red;
if( green > max) max = green;
if( blue > max) max = blue;
// stop div/0 when color is black
if(max > 0) {
uint16_t factor = ((uint16_t)(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(uint8_t 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
FASTLED_FORCE_INLINE CRGB lerp8( const CRGB& other, fract8 frac) 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 uint8_t getParity()
{
uint8_t 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( uint8_t parity)
{
uint8_t 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}
} 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)
{
uint16_t 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)
{
uint16_t 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)
{
uint16_t 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)
{
uint16_t 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, uint8_t 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, uint8_t d);
/// Scale using CRGB::nscale8_video()
FASTLED_FORCE_INLINE CRGB operator%( const CRGB& p1, uint8_t d);
/// @} PixelTypes
FASTLED_NAMESPACE_END

233
FastLED/src/crgb.hpp Normal file
View File

@@ -0,0 +1,233 @@
/// @file crgb.hpp
/// Defines utility functions for the red, green, and blue (RGB) pixel struct
#pragma once
#include <stdint.h>
#include "chsv.h"
#include "crgb.h"
#include "lib8tion.h"
#include "fl/namespace.h"
#include "fl/force_inline.h"
#include "fl/str.h"
#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
/// Add one CRGB to another, saturating at 0xFF for each channel
FASTLED_FORCE_INLINE CRGB& CRGB::operator+= (const CRGB& rhs )
{
r = qadd8( r, rhs.r);
g = qadd8( g, rhs.g);
b = qadd8( b, rhs.b);
return *this;
}
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;
}
inline CRGB& CRGB::fadeToBlackBy (uint8_t fadefactor )
{
nscale8x3( r, g, b, 255 - fadefactor);
return *this;
}
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::lerp8( const CRGB& other, fract8 frac) const
{
CRGB ret;
ret.r = lerp8by8(r,other.r,frac);
ret.g = lerp8by8(g,other.g,frac);
ret.b = lerp8by8(b,other.b,frac);
return ret;
}
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

17
FastLED/src/dither_mode.h Normal file
View File

@@ -0,0 +1,17 @@
/// @file dither_mode.h
/// Declares dithering options and types
#pragma once
#include <stdint.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 uint8_t EDitherMode;
FASTLED_NAMESPACE_END

88
FastLED/src/dmx.h Normal file
View 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

View File

@@ -0,0 +1,5 @@
#pragma once
#warning "This header is deprecated. Please use engine_events.h, this header will go away in the version 4.0."
#include "fl/engine_events.h"

33
FastLED/src/eorder.h Normal file
View File

@@ -0,0 +1,33 @@
/// @file eorder.h
/// Defines color channel ordering enumerations
#pragma once
#include "fl/namespace.h"
FASTLED_NAMESPACE_BEGIN
/// 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
};
FASTLED_NAMESPACE_END

View 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

218
FastLED/src/fastled_delay.h Normal file
View File

@@ -0,0 +1,218 @@
#ifndef __INC_FL_DELAY_H
#define __INC_FL_DELAY_H
#include "FastLED.h"
#include "fl/types.h"
#include "fl/force_inline.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
uint16_t mLastMicros;
public:
/// Constructor
CMinWait() { mLastMicros = 0; }
/// Blocking delay until WAIT time since mark() has passed
void wait() {
uint16_t 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 uint32_t termination = 4294966398 / 10;
const uint32_t remainder = 4294966398 % 10;
for (uint32_t 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

View File

@@ -0,0 +1,103 @@
#ifndef __INC_FL_PROGMEM_H
#define __INC_FL_PROGMEM_H
#include "fl/namespace.h"
/// @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.
//empty PROGMEM simulation
#define FL_PROGMEM
#define FL_PGM_READ_BYTE_NEAR(x) (*((const uint8_t*)(x)))
#define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_t*)(x)))
#define FL_PGM_READ_DWORD_NEAR(x) (*((const uint32_t*)(x)))
#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.
#if defined(FASTLED_ARM) || defined(ESP32) || defined(ESP8266) || defined(FASTLED_DOXYGEN)
#define FL_ALIGN_PROGMEM __attribute__ ((aligned (4)))
#else
#define FL_ALIGN_PROGMEM
#endif
#endif

396
FastLED/src/fastpin.h Normal file
View File

@@ -0,0 +1,396 @@
#ifndef __INC_FASTPIN_H
#define __INC_FASTPIN_H
#include "FastLED.h"
#include "led_sysdefs.h"
#include <stddef.h>
#include "fl/unused.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_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
uint8_t 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); }
/// Set the pin state to `HIGH`
inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; }
/// Set the pin state to `LOW`
inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; }
/// 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;
uint8_t mPin;
void _init() {
// TODO: fill in init on a new platform
mPinMask = 0;
mPort = NULL;
mInPort = NULL;
}
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 hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; }
inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; }
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<uint8_t 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<uint8_t PIN> RwReg FastPin<PIN>::sPinMask;
template<uint8_t PIN> volatile RwReg *FastPin<PIN>::sPort;
template<uint8_t PIN> volatile RoReg *FastPin<PIN>::sInPort;
#else
template<uint8_t 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(), "Invalid pin specified");
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<uint8_t PIN> class FastPinBB : public FastPin<PIN> {};
typedef volatile uint32_t & reg32_t; ///< Reference to a 32-bit register, volatile
typedef volatile uint32_t * 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<uint8_t 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

197
FastLED/src/fastspi.h Normal file
View File

@@ -0,0 +1,197 @@
/// @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"
#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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _SPI_CLOCK_DIVIDER>
class SPIOutput : public StubSPIOutput {};
#else
#if !defined(FASTLED_ALL_PINS_HARDWARE_SPI)
/// Hardware SPI output
template<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _SPI_CLOCK_DIVIDER>
class SPIOutput : public AVRSoftwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
#endif
/// Software SPI output
template<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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<uint32_t SPI_SPEED>
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED, 0x4002C000> {};
#if defined(SPI2_DATA)
template<uint32_t SPI_SPEED>
class SPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI2_DATA, SPI2_CLOCK, SPI_SPEED, 0x4002C000> {};
template<uint32_t SPI_SPEED>
class SPIOutput<SPI_DATA, SPI2_CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<SPI_DATA, SPI2_CLOCK, SPI_SPEED, 0x4002C000> {};
template<uint32_t 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<uint32_t SPI_SPEED>
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public Teensy4HardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED, SPI, 0> {};
template<uint32_t SPI_SPEED>
class SPIOutput<SPI1_DATA, SPI1_CLOCK, SPI_SPEED> : public Teensy4HardwareSPIOutput<SPI1_DATA, SPI1_CLOCK, SPI_SPEED, SPI1, 1> {};
template<uint32_t 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<uint32_t SPI_SPEED>\
class SPIOutput<__DATA, __CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<__DATA, __CLOCK, SPI_SPEED, 0x40076000> {};
#define DECLARE_SPI1(__DATA,__CLOCK) template<uint32_t 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<uint32_t SPI_SPEED>
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public SAMHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> {};
#elif defined(AVR_HARDWARE_SPI)
template<uint32_t SPI_SPEED>
class SPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> : public AVRHardwareSPIOutput<SPI_DATA, SPI_CLOCK, SPI_SPEED> {};
#if defined(SPI_UART0_DATA)
template<uint32_t 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<uint32_t 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<uint32_t 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 "No hardware SPI pins defined. All SPI access will default to bitbanged output"
# else
# warning "No hardware SPI pins defined. All SPI access will default to bitbanged output"
# 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 SPI for you!"
# else
# warning "Forcing software SPI - no hardware SPI for you!"
# endif
# endif
#endif
#endif // !defined(FASTLED_STUB_IMPL)
FASTLED_NAMESPACE_END
#endif

View File

@@ -0,0 +1,421 @@
/// @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"
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, uint32_t 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(uint16_t 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

View File

@@ -0,0 +1,3 @@
/// @file fastspi_dma.h
/// Direct memory access (DMA) functions for SPI interfaces
/// @deprecated This header file is empty.

71
FastLED/src/fastspi_nop.h Normal file
View File

@@ -0,0 +1,71 @@
/// @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"
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 <uint8_t _DATA_PIN, uint8_t _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 <uint8_t BIT> inline static void writeBit(uint8_t b) { /* TODO */ }
/// write a byte out via SPI (returns immediately on writing register)
void writeByte(uint8_t 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(uint8_t value, int len) { /* TODO */ }
/// A full cycle of writing a value for len bytes, including select, release, and waiting
void writeBytesValue(uint8_t value, int len) { /* TODO */ }
/// A full cycle of writing a raw block of data out, including select, release, and waiting
void writeBytes(uint8_t *data, int len) { /* TODO */ }
/// write a single bit out, which bit from the passed in byte is determined by template parameter
template <uint8_t BIT> inline static void writeBit(uint8_t b) { /* TODO */ }
/// write out pixel data from the given PixelController object
template <uint8_t FLAGS, class D, EOrder RGB_ORDER> void writePixels(PixelController<RGB_ORDER> pixels, void* context = NULL) { /* TODO */ }
};
FASTLED_NAMESPACE_END
#endif
#endif

103
FastLED/src/fastspi_ref.h Normal file
View File

@@ -0,0 +1,103 @@
/// @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"
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 <uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _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(uint8_t 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(uint8_t value, int len) {
while(len--) { writeByte(value); }
}
/// A full cycle of writing a value for len bytes, including select, release, and waiting
void writeBytesValue(uint8_t 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 uint8_t *data, int len) {
uint8_t *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 uint8_t *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 <uint8_t BIT> inline static void writeBit(uint8_t 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 <uint8_t 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

View File

@@ -0,0 +1,87 @@
/// @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

View File

@@ -0,0 +1,5 @@
#pragma once
#warning "This header is deprecated. Please use file_system.h, this header will go away in the version 4.0."
#include "fl/file_system.h"

12
FastLED/src/fl/_readme Normal file
View File

@@ -0,0 +1,12 @@
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.

View File

@@ -0,0 +1,55 @@
#include <stdlib.h>
#include "fl/allocator.h"
#include "fl/namespace.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(size_t 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(size_t size) { return malloc(size); }
void DefaultFree(void *ptr) { free(ptr); }
#endif
void *(*Alloc)(size_t) = DefaultAlloc;
void (*Free)(void *) = DefaultFree;
} // namespace
void SetLargeBlockAllocator(void *(*alloc)(size_t), void (*free)(void *)) {
Alloc = alloc;
Free = free;
}
void* LargeBlockAllocate(size_t size, bool zero) {
void* ptr = Alloc(size);
if (zero) {
memset(ptr, 0, size);
}
return ptr;
}
void LargeBlockDeallocate(void* ptr) {
Free(ptr);
}
} // namespace fl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <stddef.h>
#include <string.h>
namespace fl {
void SetLargeBlockAllocator(void *(*alloc)(size_t), void (*free)(void *));
void* LargeBlockAllocate(size_t size, bool zero = true);
void LargeBlockDeallocate(void* ptr);
template<typename T>
class LargeBlockAllocator {
public:
static T* Alloc(size_t n) {
void* ptr = LargeBlockAllocate(sizeof(T) * n, true);
return reinterpret_cast<T*>(ptr);
}
static void Free(T* p) {
if (p == nullptr) {
return;
}
LargeBlockDeallocate(p);
}
};
} // namespace fl

25
FastLED/src/fl/assert.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "fl/warn.h"
#include "fl/strstream.h"
#ifndef DEBUG
#define FASTLED_ASSERT(x, MSG) FASTLED_WARN_IF(!(x), MSG)
#else
#ifdef ESP32
#include "esp_log.h"
#include "esp_check.h"
#define FASTLED_ASSERT(x, MSG) \
{ \
if (!(x)) { \
ESP_LOGE("#### FastLED", "%s", (fl::StrStream() << MSG).c_str()); \
ESP_ERROR_CHECK(ESP_FAIL); \
} \
}
#else
#define FASTLED_ASSERT(x, MSG) FASTLED_WARN_IF(!(x), MSG)
#endif
#endif

View File

@@ -0,0 +1,72 @@
#include "bilinear_compression.h"
#include "crgb.h"
#include "fl/math_macros.h"
namespace fl {
void downscaleBilinear(const CRGB *src, uint16_t srcWidth, uint16_t srcHeight,
CRGB *dst, uint16_t dstWidth, uint16_t dstHeight) {
// Use 8 bits for fixed-point fractional precision.
const uint16_t SHIFT = 8;
const uint16_t FP_ONE = 1 << SHIFT; // 256 in fixed-point
const uint16_t WEIGHT_SHIFT =
SHIFT * 2; // 16 bits (for the product of two fixed-point numbers)
const uint16_t rounding = 1 << (WEIGHT_SHIFT - 1);
// Compute scale factors in fixed-point: factor = (srcDimension << SHIFT) /
// dstDimension.
uint16_t scaleX = (srcWidth << SHIFT) / dstWidth;
uint16_t scaleY = (srcHeight << SHIFT) / dstHeight;
// Loop over each pixel in the destination image.
for (int y = 0; y < dstHeight; ++y) {
// Compute the corresponding source y coordinate in fixed-point.
int srcYFixed = y * scaleY;
int y0 = srcYFixed >> SHIFT; // integer part for row index
int yFrac = srcYFixed & (FP_ONE - 1); // fractional part
int y1 =
MIN(y0 + 1, srcHeight - 1); // ensure we don't exceed bounds
for (int x = 0; x < dstWidth; ++x) {
// Compute the corresponding source x coordinate in fixed-point.
int srcXFixed = x * scaleX;
int x0 = srcXFixed >> SHIFT; // integer part for column index
int xFrac = srcXFixed & (FP_ONE - 1); // fractional part
int x1 = MIN(x0 + 1, srcWidth - 1);
// Compute the weights for the four neighboring pixels.
int w00 = (FP_ONE - xFrac) * (FP_ONE - yFrac);
int w10 = xFrac * (FP_ONE - yFrac);
int w01 = (FP_ONE - xFrac) * yFrac;
int w11 = xFrac * yFrac;
// Apply fixed-point weighted sum for each color channel.
int r = (w00 * src[y0 * srcWidth + x0].r +
w10 * src[y0 * srcWidth + x1].r +
w01 * src[y1 * srcWidth + x0].r +
w11 * src[y1 * srcWidth + x1].r + rounding) >>
WEIGHT_SHIFT;
int g = (w00 * src[y0 * srcWidth + x0].g +
w10 * src[y0 * srcWidth + x1].g +
w01 * src[y1 * srcWidth + x0].g +
w11 * src[y1 * srcWidth + x1].g + rounding) >>
WEIGHT_SHIFT;
int b = (w00 * src[y0 * srcWidth + x0].b +
w10 * src[y0 * srcWidth + x1].b +
w01 * src[y1 * srcWidth + x0].b +
w11 * src[y1 * srcWidth + x1].b + rounding) >>
WEIGHT_SHIFT;
// Store the computed pixel in the destination image.
dst[y * dstWidth + x] = CRGB(static_cast<unsigned char>(r),
static_cast<unsigned char>(g),
static_cast<unsigned char>(b));
}
}
}
} // namespace fl

View File

@@ -0,0 +1,17 @@
/*
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 "crgb.h"
namespace fl {
void downscaleBilinear(const CRGB *src, uint16_t srcWidth, uint16_t srcHeight,
CRGB *dst, uint16_t dstWidth, uint16_t dstHeight);
} // namespace fl

View File

@@ -0,0 +1,273 @@
/// @file bilinear_expansion.cpp
/// @brief Demonstrates how to mix noise generation with color palettes on a
/// 2D LED matrix
#include <stdint.h>
#include "bilinear_expansion.h"
#include "crgb.h"
#include "fl/namespace.h"
#include "fl/xymap.h"
namespace fl {
uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, uint16_t dx, uint16_t dy);
uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, uint8_t dx, uint8_t dy);
void bilinearExpandArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
uint16_t inputHeight, XYMap xyMap) {
uint16_t n = xyMap.getTotal();
uint16_t outputWidth = xyMap.getWidth();
uint16_t outputHeight = xyMap.getHeight();
const uint16_t scale_factor = 256; // Using 8 bits for the fractional part
for (uint16_t y = 0; y < outputHeight; y++) {
for (uint16_t x = 0; x < outputWidth; x++) {
// Calculate the corresponding position in the input grid
uint32_t fx = ((uint32_t)x * (inputWidth - 1) * scale_factor) /
(outputWidth - 1);
uint32_t fy = ((uint32_t)y * (inputHeight - 1) * scale_factor) /
(outputHeight - 1);
uint16_t ix = fx / scale_factor; // Integer part of x
uint16_t iy = fy / scale_factor; // Integer part of y
uint16_t dx = fx % scale_factor; // Fractional part of x
uint16_t dy = fy % scale_factor; // Fractional part of y
uint16_t ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
uint16_t iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
uint16_t i00 = iy * inputWidth + ix;
uint16_t i10 = iy * inputWidth + ix1;
uint16_t i01 = iy1 * inputWidth + ix;
uint16_t i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolate(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolate(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolate(c00.b, c10.b, c01.b, c11.b, dx, dy);
uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11,
uint16_t dx, uint16_t dy) {
uint16_t dx_inv = 256 - dx;
uint16_t dy_inv = 256 - dy;
uint32_t w00 = (uint32_t)dx_inv * dy_inv;
uint32_t w10 = (uint32_t)dx * dy_inv;
uint32_t w01 = (uint32_t)dx_inv * dy;
uint32_t w11 = (uint32_t)dx * dy;
uint32_t sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Normalize the result by dividing by 65536 (shift right by 16 bits),
// with rounding
uint8_t result = (uint8_t)((sum + 32768) >> 16);
return result;
}
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, uint8_t inputHeight, XYMap xyMap) {
uint8_t width = xyMap.getWidth();
uint8_t height = xyMap.getHeight();
if (width != xyMap.getWidth() || height != xyMap.getHeight()) {
// xyMap has width and height that do not fit in an uint16_t.
return;
}
uint16_t n = xyMap.getTotal();
for (uint8_t y = 0; y < height; y++) {
for (uint8_t x = 0; x < width; x++) {
// Use 8-bit fixed-point arithmetic with 8 fractional bits
// (scale factor of 256)
uint16_t fx = ((uint16_t)x * (inputWidth - 1) * 256) / (width - 1);
uint16_t fy =
((uint16_t)y * (inputHeight - 1) * 256) / (height - 1);
uint8_t ix = fx >> 8; // Integer part
uint8_t iy = fy >> 8;
uint8_t dx = fx & 0xFF; // Fractional part
uint8_t dy = fy & 0xFF;
uint8_t ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
uint8_t iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
uint16_t i00 = iy * inputWidth + ix;
uint16_t i10 = iy * inputWidth + ix1;
uint16_t i01 = iy1 * inputWidth + ix;
uint16_t i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolatePowerOf2(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolatePowerOf2(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolatePowerOf2(c00.b, c10.b, c01.b, c11.b, dx, dy);
uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, uint8_t dx, uint8_t dy) {
uint16_t dx_inv = 256 - dx; // 0 to 256
uint16_t dy_inv = 256 - dy; // 0 to 256
// Scale down weights to fit into uint16_t
uint16_t w00 = (dx_inv * dy_inv) >> 8; // Max value 256
uint16_t w10 = (dx * dy_inv) >> 8;
uint16_t w01 = (dx_inv * dy) >> 8;
uint16_t w11 = (dx * dy) >> 8;
// Sum of weights should be approximately 256
uint16_t weight_sum = w00 + w10 + w01 + w11;
// Compute the weighted sum of pixel values
uint16_t sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Normalize the result
uint8_t result = (sum + (weight_sum >> 1)) / weight_sum;
return result;
}
// Floating-point version of bilinear interpolation
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, float dx, float dy) {
float dx_inv = 1.0f - dx;
float dy_inv = 1.0f - dy;
// Calculate the weights for each corner
float w00 = dx_inv * dy_inv;
float w10 = dx * dy_inv;
float w01 = dx_inv * dy;
float w11 = dx * dy;
// Compute the weighted sum
float sum = v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11;
// Clamp the result to [0, 255] and round
uint8_t result = static_cast<uint8_t>(sum + 0.5f);
return result;
}
// Floating-point version for arbitrary grid sizes
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
uint16_t inputWidth, uint16_t inputHeight,
XYMap xyMap) {
uint16_t n = xyMap.getTotal();
uint16_t outputWidth = xyMap.getWidth();
uint16_t outputHeight = xyMap.getHeight();
for (uint16_t y = 0; y < outputHeight; y++) {
for (uint16_t x = 0; x < outputWidth; x++) {
// Map output pixel to input grid position
float fx = static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
float fy = static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
uint16_t ix = static_cast<uint16_t>(fx);
uint16_t iy = static_cast<uint16_t>(fy);
float dx = fx - ix;
float dy = fy - iy;
uint16_t ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
uint16_t iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
uint16_t i00 = iy * inputWidth + ix;
uint16_t i10 = iy * inputWidth + ix1;
uint16_t i01 = iy1 * inputWidth + ix;
uint16_t i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
// Floating-point version for power-of-two grid sizes
void bilinearExpandFloat(const CRGB *input, CRGB *output,
uint8_t inputWidth, uint8_t inputHeight,
XYMap xyMap) {
uint8_t outputWidth = xyMap.getWidth();
uint8_t outputHeight = xyMap.getHeight();
if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) {
// xyMap has width and height that do not fit in a uint8_t.
return;
}
uint16_t n = xyMap.getTotal();
for (uint8_t y = 0; y < outputHeight; y++) {
for (uint8_t x = 0; x < outputWidth; x++) {
// Map output pixel to input grid position
float fx = static_cast<float>(x) * (inputWidth - 1) / (outputWidth - 1);
float fy = static_cast<float>(y) * (inputHeight - 1) / (outputHeight - 1);
uint8_t ix = static_cast<uint8_t>(fx);
uint8_t iy = static_cast<uint8_t>(fy);
float dx = fx - ix;
float dy = fy - iy;
uint8_t ix1 = (ix + 1 < inputWidth) ? ix + 1 : ix;
uint8_t iy1 = (iy + 1 < inputHeight) ? iy + 1 : iy;
uint16_t i00 = iy * inputWidth + ix;
uint16_t i10 = iy * inputWidth + ix1;
uint16_t i01 = iy1 * inputWidth + ix;
uint16_t i11 = iy1 * inputWidth + ix1;
CRGB c00 = input[i00];
CRGB c10 = input[i10];
CRGB c01 = input[i01];
CRGB c11 = input[i11];
CRGB result;
result.r = bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
result.g = bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
result.b = bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) {
output[idx] = result;
}
}
}
}
} // namespace fl

View File

@@ -0,0 +1,62 @@
/// @file bilinear_expansion.h
/// @brief Demonstrates how to mix noise generation with color palettes on a
/// 2D LED matrix
#pragma once
#include <stdint.h>
#include "crgb.h"
#include "fl/namespace.h"
#include "fl/xymap.h"
namespace fl {
/// @brief Performs bilinear interpolation for upscaling an image.
/// @param output The output grid to write into the interpolated values.
/// @param input The input grid to read from.
/// @param inputWidth The width of the input grid.
/// @param inputHeight The height of the input grid.
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
/// pixel is mapped outside of the range then it is clipped.
void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
uint16_t inputWidth, uint16_t inputHeight,
fl::XYMap xyMap);
/// @brief Performs bilinear interpolation for upscaling an image.
/// @param output The output grid to write into the interpolated values.
/// @param input The input grid to read from.
/// @param inputWidth The width of the input grid.
/// @param inputHeight The height of the input grid.
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
/// pixel is mapped outside of the range then it is clipped.
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, uint8_t inputHeight, fl::XYMap xyMap);
//
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
uint16_t inputHeight, fl::XYMap xyMap) {
uint16_t outputWidth = xyMap.getWidth();
uint16_t outputHeight = xyMap.getHeight();
const bool wontFit = (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight());
// if the input dimensions are not a power of 2 then we can't use the
// optimized version.
if (wontFit || (inputWidth & (inputWidth - 1)) || (inputHeight & (inputHeight - 1))) {
bilinearExpandArbitrary(input, output, inputWidth, inputHeight, xyMap);
} else {
bilinearExpandPowerOf2(input, output, inputWidth, inputHeight, xyMap);
}
}
void bilinearExpandFloat(const CRGB *input, CRGB *output,
uint8_t inputWidth, uint8_t inputHeight,
fl::XYMap xyMap);
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
uint16_t inputWidth, uint16_t inputHeight,
fl::XYMap xyMap);
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, float dx, float dy);
} // namespace fl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "fl/namespace.h"
#include "fl/ptr.h"
#include "crgb.h"
namespace fl {
FASTLED_SMART_PTR(ByteStream);
// An abstract class that represents a stream of bytes.
class ByteStream : public fl::Referent {
public:
virtual ~ByteStream() {}
virtual bool available(size_t) const = 0;
virtual size_t read(uint8_t *dst, size_t bytesToRead) = 0;
virtual const char *path() const = 0;
virtual void close() {} // default is do nothing on close.
// convenience functions
size_t readCRGB(CRGB *dst, size_t n) {
return read((uint8_t *)dst, n * 3) / 3;
}
};
} // namespace fl

View File

@@ -0,0 +1,68 @@
#include <string.h>
#include "fl/bytestreammemory.h"
#include "fl/math_macros.h"
#include "fl/namespace.h"
#include "fl/warn.h"
namespace fl {
ByteStreamMemory::ByteStreamMemory(uint32_t size_buffer)
: mReadBuffer(size_buffer) {}
ByteStreamMemory::~ByteStreamMemory() = default;
bool ByteStreamMemory::available(size_t n) const {
return mReadBuffer.size() >= n;
}
size_t ByteStreamMemory::read(uint8_t *dst, size_t bytesToRead) {
if (!available(bytesToRead) || dst == nullptr) {
FASTLED_WARN("ByteStreamMemory::read: !available(bytesToRead): " << bytesToRead << " mReadBuffer.size(): " << mReadBuffer.size());
return 0;
}
size_t actualBytesToRead = MIN(bytesToRead, mReadBuffer.size());
size_t bytesRead = 0;
while (bytesRead < actualBytesToRead) {
uint8_t& b = dst[bytesRead];
mReadBuffer.pop_front(&b);
bytesRead++;
}
if (bytesRead == 0) {
FASTLED_WARN("ByteStreamMemory::read: bytesRead == 0");
}
return bytesRead;
}
size_t ByteStreamMemory::write(const uint8_t* src, size_t 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;
}
size_t written = 0;
for (size_t 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;
}
size_t ByteStreamMemory::writeCRGB(const CRGB* src, size_t n) {
size_t bytes_written = write(reinterpret_cast<const uint8_t*>(src), n * 3);
size_t pixels_written = bytes_written / 3;
return pixels_written;
}
} // namespace fl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "fl/namespace.h"
#include "fl/ptr.h"
#include "fl/bytestream.h"
#include "fl/circular_buffer.h"
namespace fl {
FASTLED_SMART_PTR(ByteStreamMemory);
class ByteStreamMemory : public ByteStream {
public:
ByteStreamMemory(uint32_t size_buffer);
~ByteStreamMemory() override;
bool available(size_t n) const override;
size_t read(uint8_t *dst, size_t bytesToRead) override;
void clear() {
mReadBuffer.clear();
}
const char *path() const override { return "ByteStreamMemory"; }
size_t write(const uint8_t* src, size_t n);
size_t writeCRGB(const CRGB* src, size_t n);
private:
CircularBuffer<uint8_t> mReadBuffer;
};
} // namespace fl

36
FastLED/src/fl/callback.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include "fl/namespace.h"
namespace fl {
/// @brief A void returning callback that can be used to call a function with a variable number of arguments. If this binds
/// to a non-static member function then self must be none null. If it's a free function then set self to
/// nullptr.
/// @tparam ...Args The types of the arguments to the function.
/// @example Callback<const char*> callback(this, [](void* self, const char* str) { static_cast<MyClass*>(self)->myFunction(str); });
template<typename ...Args>
class Callback {
public:
Callback() = default;
// Member function constructor.
Callback(void* self, void (*callback)(void* self, Args... args)) : self(self), callback(callback) {}
// Free function constructor.
explicit Callback(void* (*callback)(Args... args)) : self(nullptr), callback((void (*)(void*, Args...))callback) {}
Callback(const Callback&) = default;
operator bool() const { return callback != nullptr; }
void operator()(Args... args) const { if (callback) callback(self, args...); }
void clear() { callback = nullptr; self = nullptr; }
bool operator==(const Callback& other) const { return self == other.self && callback == other.callback; }
bool operator!=(const Callback& other) const { return !(*this == other); }
Callback& operator=(const Callback& other) { self = other.self; callback = other.callback; return *this; }
Callback& operator=(void* (*other)(Args... args)) { self = nullptr; callback = (void (*)(void*, Args...))other; return *this; }
Callback& operator=(void (*other)(void* self, Args... args)) { self = nullptr; callback = other; return *this; }
// operator < for set/map
bool operator<(const Callback& other) const { return self < other.self || (self == other.self && callback < other.callback); }
private:
void* self = nullptr;
void (*callback)(void* self, Args... args) = nullptr;
};
} // namespace fl

View File

@@ -0,0 +1,122 @@
#pragma once
#include <stddef.h> // For size_t
#include <stdint.h> // For standard integer types
#include "fl/namespace.h"
#include "fl/ptr.h" // Assuming this provides `scoped_array` or similar
#include "fl/math_macros.h"
namespace fl {
template <typename T>
class CircularBuffer {
public:
CircularBuffer(size_t capacity)
: mCapacity(capacity + 1), mHead(0), mTail(0) { // Extra space for distinguishing full/empty
mBuffer.reset(new T[mCapacity]);
}
CircularBuffer(const CircularBuffer&) = delete;
CircularBuffer& operator=(const CircularBuffer&) = 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[](size_t index) {
return mBuffer[(mTail + index) % mCapacity];
}
const T& operator[](size_t index) const {
return mBuffer[(mTail + index) % mCapacity];
}
size_t size() const {
return (mHead + mCapacity - mTail) % mCapacity;
}
size_t capacity() const {
return mCapacity - 1;
}
bool empty() const {
return mHead == mTail;
}
bool full() const {
return increment(mHead) == mTail;
}
void clear() {
mHead = mTail = 0;
}
private:
size_t increment(size_t index) const {
return (index + 1) % mCapacity;
}
size_t decrement(size_t index) const {
return (index + mCapacity - 1) % mCapacity;
}
fl::scoped_array<T> mBuffer;
size_t mCapacity;
size_t mHead;
size_t mTail;
};
}

13
FastLED/src/fl/convert.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
// Conversion from FastLED timings to the type found in datasheets.
inline void convert_fastled_timings_to_timedeltas(
uint16_t T1, uint16_t T2, uint16_t T3,
uint16_t* T0H, uint16_t* T0L, uint16_t* T1H, uint16_t* T1L) {
*T0H = T1;
*T0L = T2 + T3;
*T1H = T1 + T2;
*T1L = T3;
}

69
FastLED/src/fl/dbg.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include "fl/strstream.h"
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
#ifdef __EMSCRIPTEN__
#define FASTLED_DBG_USE_PRINTF 1
#endif
#ifndef FASTLED_DBG_USE_PRINTF
#if defined(DEBUG) && (defined(__IMXRT1062__) || defined(ESP32))
#define FASTLED_DBG_USE_PRINTF 1
#else
#define FASTLED_DBG_USE_PRINTF 0
#endif
#endif
#if FASTLED_DBG_USE_PRINTF
#define FASTLED_HAS_DBG 1
#include <stdio.h> // ok include
namespace fl {
} // namespace fl
#define _FASTLED_DGB(X) \
printf("%s", \
(fl::StrStream() << \
(fl::fastled_file_offset(__FILE__)) << "(" << __LINE__ << "): " << X << "\n") \
.c_str())
#define FASTLED_DBG(X) _FASTLED_DGB(X)
#endif
#ifndef FASTLED_HAS_DBG
// FASTLED_DBG is a macro that can be defined to enable debug printing.
#define FASTLED_DBG(X) (fl::FakeStrStream() << X)
#endif
#ifndef FASTLED_DBG_IF
#ifdef FASTLED_HAS_DBG
#define FASTLED_DBG_IF(COND, MSG) if (COND) FASTLED_DBG(MSG)
#else
#define FASTLED_DBG_IF(COND, MSG) while(false && (COND)) { FASTLED_DBG(MSG); }
#endif // FASTLED_HAS_DBG
#endif // FASTLED_DBG_IF

View File

@@ -0,0 +1,13 @@
#if defined(__GNUC__) // GCC or Clang
# ifndef FASTLED_DEPRECATED
# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
# define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg)))
# else
# define FASTLED_DEPRECATED(msg) __attribute__((deprecated))
# endif
# endif
#else // Other compilers
# ifndef FASTLED_DEPRECATED
# define FASTLED_DEPRECATED(msg)
# endif
#endif

View File

@@ -0,0 +1,132 @@
#include "fl/engine_events.h"
#include "fl/namespace.h"
using namespace fl;
FASTLED_NAMESPACE_BEGIN
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
}
EngineEvents* EngineEvents::getInstance() {
#if FASTLED_HAS_ENGINE_EVENTS
return &Singleton<EngineEvents>::instance();
#else
return nullptr; // strip out when engine events are disabled.
#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, uint32_t 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
FASTLED_NAMESPACE_END

View File

@@ -0,0 +1,153 @@
#pragma once
#include "fl/vector.h"
#include "fl/singleton.h"
#include "fl/xymap.h"
#include "fl/screenmap.h"
#include "fl/namespace.h"
#ifndef FASTLED_ENGINE_EVENTS_MAX_LISTENERS
#define FASTLED_ENGINE_EVENTS_MAX_LISTENERS 8
#endif
#ifndef FASTLED_HAS_ENGINE_EVENTS
#ifdef __AVR__
#define FASTLED_HAS_ENGINE_EVENTS 0
#else
#define FASTLED_HAS_ENGINE_EVENTS 1
#endif
#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, uint32_t 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, uint32_t 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
}
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, uint32_t num_leds);
void _onCanvasUiSet(CLEDController *strip, const ScreenMap& xymap);
void _onPlatformPreLoop();
bool _hasListener(Listener *listener);
#if FASTLED_HAS_ENGINE_EVENTS
struct Pair {
Listener *listener = nullptr;
int priority = 0;
Pair(Listener *listener, int priority) : listener(listener), priority(priority) {}
};
typedef fl::FixedVector<Pair, FASTLED_ENGINE_EVENTS_MAX_LISTENERS>
ListenerList;
ListenerList mListeners;
#endif
static EngineEvents *getInstance();
EngineEvents() = default;
friend class fl::Singleton<EngineEvents>;
};
} // namespace fl

View File

@@ -0,0 +1,197 @@
#include "fl/file_system.h"
#include "fl/warn.h"
#include "fl/unused.h"
#ifdef __EMSCRIPTEN__
#include "platforms/wasm/fs_wasm.h"
#elif __has_include(<SD.h>)
// work in progress.
//#include "platforms/fs_sdcard_arduino.hpp"
#endif
#include "fl/namespace.h"
#include "fl/json.h"
#include "fl/unused.h"
#include "fl/screenmap.h"
namespace fl {
class NullFileHandle : public FileHandle {
public:
NullFileHandle() = default;
~NullFileHandle() override {}
bool available() const override { return false; }
size_t size() const override { return 0; }
size_t read(uint8_t *dst, size_t bytesToRead) override {
FASTLED_UNUSED(dst);
FASTLED_UNUSED(bytesToRead);
return 0;
}
size_t pos() const override { return 0; }
const char *path() const override {
return "NULL FILE HANDLE";
}
bool seek(size_t pos) override {
FASTLED_UNUSED(pos);
return false;
}
void close() override {}
bool valid() const override {
FASTLED_WARN("NullFileHandle is not valid");
return false;
}
};
using namespace fl;
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);
FileHandlePtr out = FileHandlePtr::TakeOwnership(new NullFileHandle());
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;
}
size_t FileHandle::bytesLeft() const { return size() - pos(); }
FileSystem::FileSystem() : mFs() {}
void FileSystem::end() {
if (mFs) {
mFs->end();
}
}
bool FileSystem::readJson(const char *path, JsonDocument* doc) {
Str text;
if (!readText(path, &text)) {
return false;
}
return parseJson(text.c_str(), doc);
}
bool FileSystem::readScreenMaps(const char *path, FixedMap<Str, ScreenMap, 16>* out, Str* error) {
Str text;
if (!readText(path, &text)) {
FASTLED_WARN("Failed to read file: " << path);
if (error) {
*error = "Failed to read file: ";
error->append(path);
}
return false;
}
Str 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, Str* error) {
Str text;
if (!readText(path, &text)) {
FASTLED_WARN("Failed to read file: " << path);
if (error) {
*error = "Failed to read file: ";
error->append(path);
}
return false;
}
Str 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, size_t pixelsPerFrame, float fps, size_t nFrameHistory) {
Video video(pixelsPerFrame, fps, nFrameHistory);
FileHandlePtr file = openRead(path);
if (!file) {
video.setError(fl::Str("Could not open file: ").append(path));
return video;
}
video.begin(file);
return video;
}
bool FileSystem::readText(const char *path, fl::Str* out) {
FileHandlePtr file = openRead(path);
if (!file) {
FASTLED_WARN("Failed to open file: " << path);
return false;
}
size_t size = file->size();
out->reserve(size + out->size());
bool wrote = false;
while (file->available()) {
uint8_t buf[64];
size_t 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 {
__attribute__((weak)) FsImplPtr make_sdcard_filesystem(int cs_pin) {
FASTLED_UNUSED(cs_pin);
FsImplPtr out = FsImplPtr::TakeOwnership(new NullFileSystem());
return out;
}
} // namespace fl

View File

@@ -0,0 +1,106 @@
#pragma once
// Note, fs.h breaks ESPAsyncWebServer so we use file_system.h instead.
#include <stdint.h>
#include <stddef.h>
#include "fl/namespace.h"
#include "fl/ptr.h"
#include "fx/video.h"
#include "fl/str.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);
}
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, size_t N> class FixedMap;
class JsonDocument;
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, size_t pixelsPerFrame, float fps = 30.0f, size_t nFrameHistory = 0); // Null if video could not be opened.
bool readText(const char *path, Str* out);
bool readJson(const char *path, JsonDocument* doc);
bool readScreenMaps(const char *path, FixedMap<Str, ScreenMap, 16>* out, Str* error = nullptr);
bool readScreenMap(const char *path, const char* name, ScreenMap* out, Str* 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 Referent {
public:
virtual ~FileHandle() {}
virtual bool available() const = 0;
virtual size_t bytesLeft() const;
virtual size_t size() const = 0;
virtual size_t read(uint8_t *dst, size_t bytesToRead) = 0;
virtual size_t pos() const = 0;
virtual const char* path() const = 0;
virtual bool seek(size_t pos) = 0;
virtual void close() = 0;
virtual bool valid() const = 0;
// convenience functions
size_t readCRGB(CRGB* dst, size_t n) {
return read((uint8_t*)dst, n * 3) / 3;
}
};
// Platforms will subclass this to implement the filesystem.
class FsImpl : public Referent {
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

View File

@@ -0,0 +1,161 @@
/// @file five_bit_hd_gamma.cpp
/// Defines functions for five-bit gamma correction
#define FASTLED_INTERNAL 1
#include "five_bit_hd_gamma.h"
#include "FastLED.h"
#include "fastled_progmem.h"
#include "lib8tion/intmap.h"
#include "lib8tion/math8.h"
#include "lib8tion/scale8.h"
#include "lib8tion/brightness_bitshifter.h"
#include "fl/namespace.h"
// Author: Zach Vorhies
namespace fl {
namespace {
template <typename T> T mymax(T a, T b) { return (a > b) ? a : b; }
template <typename T> T max3(T a, T b, T c) { return mymax(mymax(a, b), c); }
} // namespace
#ifndef FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8
// Fast a memory efficient gamma=2 function.
void five_bit_hd_gamma_function(CRGB color, uint16_t *r16, uint16_t *g16,
uint16_t *b16) {
uint16_t _r16 = map8_to_16(color.r);
uint16_t _g16 = map8_to_16(color.g);
uint16_t _b16 = map8_to_16(color.b);
*r16 = scale16by8(_r16, color.r);
*g16 = scale16by8(_g16, color.g);
*b16 = scale16by8(_b16, color.b);
}
#else
// Using look up table for gamma16 correction at power of 2.8
static const uint16_t PROGMEM _gamma_2_8[256] = {
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};
void five_bit_hd_gamma_function(CRGB rgb, uint16_t *r16, uint16_t *g16,
uint16_t *b16) {
*r16 = _gamma_2_8[rgb.r];
*g16 = _gamma_2_8[rgb.g];
*b16 = _gamma_2_8[rgb.b];
}
#endif // FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8
uint8_t five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16,
uint8_t brightness, CRGB *out, uint8_t *out_power_5bit) {
if (brightness == 0) {
*out = CRGB(0, 0, 0);
*out_power_5bit = 0;
return 0;
}
if (r16 == 0 && g16 == 0 && b16 == 0) {
*out = CRGB(0, 0, 0);
*out_power_5bit = (brightness <= 31) ? brightness : 31;
return brightness;
}
// Note: One day someone smarter than me will come along and invent a closed
// form solution for this. However, the numerical method works extremely
// well and has been optimized to avoid division performance penalties as
// much as possible.
// Step 1: Initialize brightness
static const uint8_t kStartBrightness = 0b00010000;
uint8_t v5 = kStartBrightness;
// Step 2: Boost brightness by swapping power with the driver brightness.
brightness_bitshifter8(&v5, &brightness, 4);
// Step 3: Boost brightness of the color channels by swapping power with the
// driver brightness.
uint16_t max_component = max3(r16, g16, b16);
// five_bit_color_bitshift(&r16, &g16, &b16, &v5);
uint8_t shifts = brightness_bitshifter16(&v5, &max_component, 4, 2);
if (shifts) {
r16 = r16 << shifts;
g16 = g16 << shifts;
b16 = b16 << shifts;
}
// Step 4: scale by final brightness factor.
if (brightness != 0xff) {
r16 = scale16by8(r16, brightness);
g16 = scale16by8(g16, brightness);
b16 = scale16by8(b16, brightness);
}
// brighten hardware brightness by turning on low order bits
if (v5 > 1) {
// since v5 is a power of two, subtracting one will invert the leading bit
// and invert all the bits below it.
// Example: 0b00010000 -1 = 0b00001111
// So 0b00010000 | 0b00001111 = 0b00011111
v5 = v5 | (v5 - 1);
}
// Step 5: Convert back to 8-bit and output.
*out = CRGB(map16_to_8(r16), map16_to_8(g16), map16_to_8(b16));
*out_power_5bit = v5;
return brightness;
}
void __builtin_five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
uint8_t global_brightness,
CRGB *out_colors,
uint8_t *out_power_5bit) {
if (global_brightness == 0) {
*out_colors = CRGB(0, 0, 0);
*out_power_5bit = 0;
return;
}
// Step 1: Gamma Correction
uint16_t 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);
}
} // namespace fl

View File

@@ -0,0 +1,94 @@
/// @file five_bit_hd_gamma.h
/// Declares functions for five-bit gamma correction
#pragma once
#include <stdint.h>
#include "fl/namespace.h"
#include "fl/force_inline.h"
#include "crgb.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(
// uint8_t r8, uint8_t g8, uint8_t b8,
// uint8_t r8_scale, uint8_t g8_scale, uint8_t b8_scale,
// uint8_t* out_r8,
// uint8_t* out_g8,
// uint8_t* out_b8,
// uint8_t* out_power_5bit) {
// cout << "hello world\n";
// }
// FASTLED_NAMESPACE_END
void __builtin_five_bit_hd_gamma_bitshift(CRGB colors,
CRGB colors_scale,
uint8_t global_brightness,
CRGB* out_colors,
uint8_t *out_power_5bit);
// Exposed for testing.
uint8_t five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16, uint8_t brightness, CRGB* out, uint8_t* 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,
uint8_t global_brightness,
CRGB* out_colors,
uint8_t *out_power_5bit);
#else
FASTLED_FORCE_INLINE void
five_bit_hd_gamma_bitshift(CRGB colors,
CRGB colors_scale,
uint8_t global_brightness,
CRGB* out_colors,
uint8_t *out_power_5bit) {
__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(
// uint8_t r8, uint8_t g8, uint8_t b8,
// uint16_t* r16, uint16_t* g16, uint16_t* 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,
uint16_t *r16, uint16_t *g16, uint16_t *b16);
#else
void five_bit_hd_gamma_function(CRGB color,
uint16_t *r16, uint16_t *g16, uint16_t *b16);
#endif // FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE
} // namespace fl

View File

@@ -0,0 +1,8 @@
#pragma once
#ifdef FASTLED_NO_FORCE_INLINE
#define FASTLED_FORCE_INLINE inline
#else
#define FASTLED_FORCE_INLINE __attribute__((always_inline)) inline
#endif

View File

@@ -0,0 +1,5 @@
#pragma once
#ifndef __has_include
#define __has_include(x) 0
#endif

View File

@@ -0,0 +1,21 @@
#pragma once
// This file must not be in the fl namespace, it must be in the global namespace.
#if (defined(__AVR__) || !defined(__has_include)) && (!defined(FASTLED_HAS_NEW))
#ifndef __has_include
#define _NO_EXCEPT
#else
#define _NO_EXCEPT noexcept
#endif
inline void* operator new(size_t, void* ptr) _NO_EXCEPT {
return ptr;
}
#elif __has_include(<new>)
#include <new>
#elif __has_include(<new.h>)
#include <new.h>
#elif __has_include("new.h")
#include "new.h"
#endif

View File

@@ -0,0 +1,16 @@
#pragma once
namespace fl {
// Because of the fixed size nature of a lot of FastLED's containers we
// need to provide additional feedback to the caller about the nature of
// why an insert did or did not happen. Specifically, we want to differentiate
// between failing to insert because the item already existed and when the container
// was full.
enum InsertResult {
kInserted = 0,
kExists = 1,
kMaxSize = 2,
};
} // namespace fl

32
FastLED/src/fl/json.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "fl/json.h"
#include "fl/str.h"
namespace fl {
bool parseJson(const char* json, fl::JsonDocument* doc, Str* _error) {
#if !FASTLED_ENABLE_JSON
if (_error) {
*_error = "JSON not enabled";
}
return false;
#else
FLArduinoJson::DeserializationError error = deserializeJson(*doc, json);
if (error) {
if (_error) {
*_error = error.c_str();
}
return false;
}
return true;
#endif
}
void toJson(const fl::JsonDocument& doc, Str* jsonBuffer) {
#if !FASTLED_ENABLE_JSON
return;
#else
serializeJson(doc, *jsonBuffer);
#endif
}
} // namespace fl

29
FastLED/src/fl/json.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#ifndef FASTLED_ENABLE_JSON
#ifdef __AVR__
#define FASTLED_ENABLE_JSON 0
#else
#define FASTLED_ENABLE_JSON 1
#endif
#endif
#if FASTLED_ENABLE_JSON
#include "third_party/arduinojson/json.h"
#endif
namespace fl {
class Str;
#if !FASTLED_ENABLE_JSON
class JsonDocument {};
#else
class JsonDocument: public ::FLArduinoJson::JsonDocument {};
#endif
bool parseJson(const char* json, JsonDocument* doc, Str* error = nullptr);
void toJson(const JsonDocument& doc, Str* jsonBuffer);
} // namespace fl

75
FastLED/src/fl/lut.h Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
/*
LUT - Look up table implementation for various types.
*/
#include <stdint.h>
#include "fl/ptr.h"
#include "fl/force_inline.h"
#include "fl/allocator.h"
#include "fl/namespace.h"
namespace fl {
template<typename T>
struct pair_xy {
T x = 0;
T y = 0;
constexpr pair_xy() = default;
constexpr pair_xy(T x, T y) : x(x), y(y) {}
};
using pair_xy_float = pair_xy<float>; // It's just easier if we allow negative values.
// LUT holds a look up table to map data from one
// value to another. This can be quite big (1/3rd of the frame buffer)
// so a Referent is used to allow memory sharing.
template<typename T>
class LUT;
typedef LUT<uint16_t> LUT16;
typedef LUT<pair_xy_float> LUTXYFLOAT;
FASTLED_SMART_PTR_NO_FWD(LUT16);
FASTLED_SMART_PTR_NO_FWD(LUTXYFLOAT);
// Templated lookup table.
template<typename T>
class LUT : public fl::Referent {
public:
LUT(uint32_t length) : length(length) {
T* ptr = LargeBlockAllocator<T>::Alloc(length);
mDataHandle.reset(ptr);
data = ptr;
}
// In this version the data is passed in but not managed by this object.
LUT(uint32_t length, T* data) : length(length) {
this->data = data;
}
~LUT() {
LargeBlockAllocator<T>::Free(mDataHandle.release());
data = mDataHandle.get();
}
const T& operator[](uint32_t index) const {
return data[index];
}
const T& operator[](uint16_t index) const {
return data[index];
}
T* getData() const {
return data;
}
private:
fl::scoped_ptr<T> mDataHandle;
T* data = nullptr;
uint32_t length;
};
} // namespace fl

407
FastLED/src/fl/map.h Normal file
View File

@@ -0,0 +1,407 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "fl/namespace.h"
#include "fl/vector.h"
#include "fl/template_magic.h"
#include "fl/insert_result.h"
#include "fl/pair.h"
#include "fl/assert.h"
namespace fl {
template<typename T>
struct DefaultLess {
bool operator()(const T& a, const T& b) const {
return a < b;
}
};
// A simple unordered map implementation with a fixed size.
// The user is responsible for making sure that the inserts
// do not exceed the capacity of the set, otherwise they will
// fail. Because of this limitation, this set is not a drop in
// replacement for std::map.
template<typename Key, typename Value, size_t N>
class FixedMap {
public:
using PairKV = fl::Pair<Key, Value>;
typedef FixedVector<PairKV, N> VectorType;
typedef typename VectorType::iterator iterator;
typedef typename VectorType::const_iterator const_iterator;
// Constructor
constexpr FixedMap() = default;
iterator begin() {
return data.begin();
}
iterator end() {
return data.end();
}
const_iterator begin() const {
return data.begin();
}
const_iterator end() const {
return data.end();
}
iterator find(const Key& key) {
for (auto it = begin(); it != end(); ++it) {
if (it->first == key) {
return it;
}
}
return end();
}
const_iterator find(const Key& key) const {
for (auto it = begin(); it != end(); ++it) {
if (it->first == key) {
return it;
}
}
return end();
}
template<typename Less>
iterator lowest(Less less_than = Less()) {
iterator lowest = end();
for (iterator it = begin(); it != end(); ++it) {
if (lowest == end() || less_than(it->first, lowest->first)) {
lowest = it;
}
}
return lowest;
}
template<typename Less>
const_iterator lowest(Less less_than = Less()) const {
const_iterator lowest = end();
for (const_iterator it = begin(); it != end(); ++it) {
if (lowest == end() || less_than(it->first, lowest->first)) {
lowest = it;
}
}
return lowest;
}
template<typename Less>
iterator highest(Less less_than = Less()) {
iterator highest = end();
for (iterator it = begin(); it != end(); ++it) {
if (highest == end() || less_than(highest->first, it->first)) {
highest = it;
}
}
return highest;
}
template<typename Less>
const_iterator highest(Less less_than = Less()) const {
const_iterator highest = end();
for (const_iterator it = begin(); it != end(); ++it) {
if (highest == end() || less_than(highest->first, it->first)) {
highest = it;
}
}
return highest;
}
// We differ from the std standard here so that we don't allow
// dereferencing the end iterator.
bool get(const Key& key, Value* value) const {
const_iterator it = find(key);
if (it != end()) {
*value = it->second;
return true;
}
return false;
}
Value get(const Key& key, bool* has=nullptr) const {
const_iterator it = find(key);
if (it != end()) {
if (has) {
*has = true;
}
return it->second;
}
if (has) {
*has = false;
}
return Value();
}
Pair<bool, iterator> insert(const Key& key, const Value& value, InsertResult* result = nullptr) {
iterator it = find(key);
if (it != end()) {
if (result) {
*result = InsertResult::kExists;
}
// return false;
return {false, it};
}
if (data.size() < N) {
data.push_back(PairKV(key, value));
if (result) {
*result = InsertResult::kInserted;
}
// return true;
return {true, data.end() - 1};
}
if (result) {
*result = InsertResult::kMaxSize;
}
//return false;
return {false, end()};
}
bool update(const Key& key, const Value& value, bool insert_if_missing = true) {
iterator it = find(key);
if (it != end()) {
it->second = value;
return true;
} else if (insert_if_missing) {
return insert(key, value).first;
}
return false;
}
Value& operator[](const Key& key) {
iterator it = find(key);
if (it != end()) {
return it->second;
}
data.push_back(PairKV(key, Value()));
return data.back().second;
}
const Value& operator[](const Key& key) const {
const_iterator it = find(key);
if (it != end()) {
return it->second;
}
static Value default_value;
return default_value;
}
bool next(const Key& key, Key* next_key, bool allow_rollover = false) const {
const_iterator it = find(key);
if (it != end()) {
++it;
if (it != end()) {
*next_key = it->first;
return true;
} else if (allow_rollover && !empty()) {
*next_key = begin()->first;
return true;
}
}
return false;
}
bool prev(const Key& key, Key* prev_key, bool allow_rollover = false) const {
const_iterator it = find(key);
if (it != end()) {
if (it != begin()) {
--it;
*prev_key = it->first;
return true;
} else if (allow_rollover && !empty()) {
*prev_key = data[data.size() - 1].first;
return true;
}
}
return false;
}
// Get the current size of the vector
constexpr size_t size() const {
return data.size();
}
constexpr bool empty() const {
return data.empty();
}
// Get the capacity of the vector
constexpr size_t capacity() const {
return N;
}
// Clear the vector
void clear() {
data.clear();
}
bool has(const Key& it) const {
return find(it) != end();
}
bool contains(const Key& key) const {
return has(key);
}
private:
VectorType data;
};
template <typename Key, typename Value, typename Less = fl::DefaultLess<Key>>
class SortedHeapMap {
private:
struct Pair {
Key first;
Value second;
Pair(const Key& k = Key(), const Value& v = Value())
: first(k), second(v) {}
};
struct PairLess {
Less less;
bool operator()(const Pair& a, const Pair& b) const {
return less(a.first, b.first);
}
};
SortedHeapVector<Pair, PairLess> data;
public:
typedef typename SortedHeapVector<Pair, PairLess>::iterator iterator;
typedef typename SortedHeapVector<Pair, PairLess>::const_iterator const_iterator;
typedef Pair value_type;
SortedHeapMap(Less less = Less())
: data(PairLess{less}) {
}
void setMaxSize(size_t n) {
data.setMaxSize(n);
}
void reserve(size_t n) {
data.reserve(n);
}
bool insert(const Key& key, const Value& value, InsertResult* result = nullptr) {
return data.insert(Pair(key, value), result);
}
void update(const Key& key, const Value& value) {
if (!insert(key, value)) {
iterator it = find(key);
it->second = value;
}
}
void swap(SortedHeapMap& other) {
data.swap(other.data);
}
Value& at(const Key& key) {
iterator it = find(key);
return it->second;
}
bool has(const Key& key) const {
return data.has(Pair(key));
}
bool contains(const Key& key) const {
return has(key);
}
bool operator==(const SortedHeapMap& other) const {
if (size() != other.size()) {
return false;
}
for (const_iterator it = begin(), other_it = other.begin();
it != end(); ++it, ++other_it) {
if (it->first != other_it->first || it->second != other_it->second) {
return false;
}
}
return true;
}
bool operator!=(const SortedHeapMap& other) const {
return !(*this == other);
}
size_t size() const { return data.size(); }
bool empty() const { return data.empty(); }
bool full() const { return data.full(); }
size_t capacity() const { return data.capacity(); }
void clear() { data.clear(); }
// begin, dend
iterator begin() { return data.begin(); }
iterator end() { return data.end(); }
const_iterator begin() const { return data.begin(); }
const_iterator end() const { return data.end(); }
iterator find(const Key& key) {
return data.find(Pair(key));
}
const_iterator find(const Key& key) const {
return data.find(Pair(key));
}
bool erase(const Key& key) {
return data.erase(Pair(key));
}
bool erase(iterator it) {
return data.erase(it);
}
iterator lower_bound(const Key& key) {
return data.lower_bound(Pair(key));
}
const_iterator lower_bound(const Key& key) const {
return data.lower_bound(Pair(key));
}
iterator upper_bound(const Key& key) {
iterator it = lower_bound(key);
if (it != end() && it->first == key) {
++it;
}
return it;
}
const_iterator upper_bound(const Key& key) const {
const_iterator it = lower_bound(key);
if (it != end() && it->first == key) {
++it;
}
return it;
}
Pair& front() { return data.front(); }
const Pair& front() const { return data.front(); }
Pair& back() { return data.back(); }
const Pair& back() const { return data.back(); }
Value& operator[](const Key& key) {
iterator it = find(key);
if (it != end()) {
return it->second;
}
Pair pair(key, Value());
bool ok = data.insert(pair);
FASTLED_ASSERT(ok, "Failed to insert into SortedHeapMap");
return data.find(pair)->second; // TODO: optimize.
}
};
} // namespace fl

View File

@@ -0,0 +1,21 @@
#pragma once
#ifndef MAX
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif
#ifndef ABS
#define ABS(x) ((x)>0?(x):-(x))
#endif
#ifndef ALMOST_EQUAL
#define ALMOST_EQUAL(a,b,small) (ABS((a)-(b))<small)
#endif
#ifndef PI
#define PI 3.1415926535897932384626433832795
#endif

View File

@@ -0,0 +1,31 @@
/// @file namespace.h
/// Implements the FastLED namespace macros
#pragma once
#if defined(FASTLED_FORCE_NAMESPACE) && !defined(FASTLED_IS_USING_NAMESPACE) && !defined(FASTLED_NAMESPACE)
#define FASTLED_NAMESPACE fl
#define FASTLED_IS_USING_NAMESPACE 1
#endif
#ifndef FASTLED_NAMESPACE
#define FASTLED_IS_USING_NAMESPACE 0
/// Start of the FastLED namespace
#define FASTLED_NAMESPACE_BEGIN
/// End of the FastLED namespace
#define FASTLED_NAMESPACE_END
/// "Using" directive for the namespace
#define FASTLED_USING_NAMESPACE
#else
#define FASTLED_IS_USING_NAMESPACE 1
#define FASTLED_NAMESPACE_BEGIN namespace FASTLED_NAMESPACE {
#define FASTLED_NAMESPACE_END }
// We need to create an empty instance of the namespace before we can
// declare that we are using it.
FASTLED_NAMESPACE_BEGIN
FASTLED_NAMESPACE_END
#define FASTLED_USING_NAMESPACE using namespace FASTLED_NAMESPACE;
#endif

12
FastLED/src/fl/pair.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
namespace fl {
template<typename Key, typename Value>
struct Pair {
Key first = Key();
Value second = Value();
Pair(const Key& k, const Value& v) : first(k), second(v) {}
};
} // namespace fl

434
FastLED/src/fl/ptr.h Normal file
View File

@@ -0,0 +1,434 @@
#pragma once
#include <stddef.h>
// FastLED smart pointer. This was originally called Ptr<T> but that conflicts with
// ArduinoJson::Ptr<T> so it was renamed to Ptr<T>.
#include "fl/namespace.h"
#include "fl/scoped_ptr.h"
#include "fl/template_magic.h"
// Declares a smart pointer. FASTLED_SMART_PTR(Foo) will declare a class FooPtr
// which will be a typedef of Ptr<Foo>. After this FooPtr::New(...args) can be
// used to create a new instance of Ptr<Foo>.
#define FASTLED_SMART_PTR(type) \
class type; \
using type##Ptr = fl::Ptr<type>;
#define FASTLED_SMART_PTR_NO_FWD(type) using type##Ptr = fl::Ptr<type>;
// If you have an interface class that you want to create a smart pointer for,
// then you need to use this to bind it to a constructor.
#define FASTLED_SMART_PTR_CONSTRUCTOR(type, constructor) \
template <> class PtrTraits<type> { \
public: \
template <typename... Args> static Ptr<type> New(Args... args) { \
fl::Ptr<type> ptr = constructor(args...); \
return ptr; \
} \
};
namespace fl {
class Referent; // Inherit this if you want your object to be able to go into a
// Ptr, or WeakPtr.
template <typename T> class Ptr; // Reference counted smart pointer base class.
template <typename T> class WeakPtr; // Weak reference smart pointer base class.
template <typename T> class Ptr;
template <typename T> class WeakPtr;
template <typename T> class PtrTraits {
public:
using element_type = T;
using ptr_type = Ptr<T>;
template <typename... Args> static Ptr<T> New(Args... args) {
T *ptr = new T(args...);
return Ptr<T>::TakeOwnership(ptr);
}
static Ptr<T> New() {
T *ptr = new T();
return Ptr<T>::TakeOwnership(ptr);
}
};
// Ptr is a reference-counted smart pointer that manages the lifetime of an
// object.
//
// It will work with any class implementing ref(), unref() and destroy().
//
// Please note that this Ptr class is "sticky" to it's referent, that is, no
// automatic conversion from raw pointers to Ptr or vice versa is allowed and
// must be done explicitly, see the Ptr::TakeOwnership() and Ptr::NoTracking()
// methods.
//
// To create a Ptr to a concrete object, it's best to use FASTLED_SMART_PTR(Foo)
// and then use FooPtr::New(...) to create a new instance of Ptr<Foo>.
//
// To create a Ptr of an interface bound to a subclass (common for driver code
// or when hiding implementation) use the Ptr<InterfaceClass>::TakeOwnership(new
// Subclass()) method.
//
// For objects created statically, use Ptr<Referent>::NoTracking(referent) to
// create a Ptr, as this will disable reference tracking but still allow it to
// be used as a Ptr.
//
// Example:
// FASTLED_SMART_PTR(Foo);
// class Foo: public fl::Referent {};
// FooPtr foo = FooPtr::New();
//
// Example 2: (Manual binding to constructor)
// class FooSubclass: public Foo {};
// Ptr<Foo> bar = Ptr<FooSubclass>::TakeOwnership(new FooSubclass());
//
// Example 3: (Provide your own constructor so that FooPtr::New() works to
// create a FooSubclass)
// class FooSubclass: public Foo { // Foo is an interface, FooSubclass is an
// implementation.
// public:
// static FooPtr New(int a, int b);
// };
// FASTLED_SMART_PTR_CONSTRUCTOR(Foo, FooSubclass::New);
// FooPtr foo = FooPtr::New(1, 2); // this will now work.
template <typename T> class Ptr : public PtrTraits<T> {
public:
friend class PtrTraits<T>;
template <typename... Args> static Ptr<T> New(Args... args) {
return PtrTraits<T>::New(args...);
}
// Used for low level allocations, typically for pointer to an
// implementation where it needs to convert to a Ptr of a base class.
static Ptr TakeOwnership(T *ptr) { return Ptr(ptr, true); }
// Used for low level allocations, typically to handle memory that is
// statically allocated where the destructor should not be called when
// the refcount reaches 0.
static Ptr NoTracking(T &referent) { return Ptr(&referent, false); }
// Allow upcasting of Refs.
template <typename U, typename = fl::is_derived<T, U>>
Ptr(const Ptr<U>& refptr) : referent_(refptr.get()) {
if (referent_ && isOwned()) {
referent_->ref();
}
}
static Ptr<T> Null() { return Ptr<T>(); }
Ptr() : referent_(nullptr) {}
// Forbidden to convert a raw pointer to a Referent into a Ptr, because
// it's possible that the raw pointer comes from the stack or static memory.
Ptr(T *referent) = delete;
Ptr &operator=(T *referent) = delete;
Ptr(const Ptr &other) : referent_(other.referent_) {
if (referent_ && isOwned()) {
referent_->ref();
}
}
Ptr(Ptr &&other) noexcept : referent_(other.referent_) {
other.referent_ = nullptr;
}
~Ptr() {
if (referent_ && isOwned()) {
referent_->unref();
}
}
Ptr &operator=(const Ptr &other) {
if (this != &other) {
if (referent_ && isOwned()) {
referent_->unref();
}
referent_ = other.referent_;
if (referent_ && isOwned()) {
referent_->ref();
}
}
return *this;
}
// Either returns the weakptr if it exists, or an empty weakptr.
WeakPtr<T> weakRefNoCreate() const;
WeakPtr<T> weakPtr() const { return WeakPtr<T>(*this); }
bool operator==(const T *other) const { return referent_ == other; }
bool operator!=(const T *other) const { return referent_ != other; }
bool operator==(const Ptr &other) const {
return referent_ == other.referent_;
}
bool operator!=(const Ptr &other) const {
return referent_ != other.referent_;
}
bool operator<(const Ptr &other) const {
return referent_ < other.referent_;
}
Ptr &operator=(Ptr &&other) noexcept {
if (this != &other) {
if (referent_ && isOwned()) {
referent_->unref();
}
referent_ = other.referent_;
other.referent_ = nullptr;
}
return *this;
}
T *get() const { return referent_; }
T *operator->() const { return referent_; }
T &operator*() const { return *referent_; }
explicit operator bool() const noexcept { return referent_ != nullptr; }
void reset() {
if (referent_ && isOwned()) {
referent_->unref();
}
referent_ = nullptr;
}
void reset(Ptr<T> &refptr) {
if (refptr.referent_ != referent_) {
if (refptr.referent_ && refptr.isOwned()) {
refptr.referent_->ref();
}
if (referent_ && isOwned()) {
referent_->unref();
}
referent_ = refptr.referent_;
}
}
// Releases the pointer from reference counting from this Ptr.
T *release() {
T *temp = referent_;
referent_ = nullptr;
return temp;
}
void swap(Ptr &other) noexcept {
T *temp = referent_;
referent_ = other.referent_;
other.referent_ = temp;
}
bool isOwned() const { return referent_ && referent_->ref_count() > 0; }
private:
Ptr(T *referent, bool from_heap) : referent_(referent) {
if (referent_ && from_heap) {
referent_->ref();
}
}
T *referent_;
};
// Don't inherit from this, this is an internal object.
class WeakReferent {
public:
WeakReferent() : mRefCount(0), mReferent(nullptr) {}
~WeakReferent() {}
void ref() { mRefCount++; }
int ref_count() const { return mRefCount; }
void unref() {
if (--mRefCount == 0) {
destroy();
}
}
void destroy() { delete this; }
void setReferent(Referent *referent) { mReferent = referent; }
Referent *getReferent() const { return mReferent; }
protected:
WeakReferent(const WeakReferent &) = default;
WeakReferent &operator=(const WeakReferent &) = default;
WeakReferent(WeakReferent &&) = default;
WeakReferent &operator=(WeakReferent &&) = default;
private:
mutable int mRefCount;
Referent *mReferent;
};
template <typename T> class WeakPtr {
public:
WeakPtr() : mWeakPtr() {}
WeakPtr(const Ptr<T> &ptr) {
if (ptr) {
WeakPtr weakRefNoCreate = ptr.weakRefNoCreate();
bool expired = weakRefNoCreate.expired();
if (expired) {
Ptr<WeakReferent> weakRefNoCreate = Ptr<WeakReferent>::New();
ptr->setWeakPtr(weakRefNoCreate);
weakRefNoCreate->setReferent(ptr.get());
}
mWeakPtr = ptr->mWeakPtr;
}
}
template <typename U> WeakPtr(const Ptr<U> &ptr) : mWeakPtr(ptr->mWeakPtr) {
if (ptr) {
WeakPtr weakRefNoCreate = ptr.weakRefNoCreate();
bool expired = weakRefNoCreate.expired();
if (expired) {
Ptr<WeakReferent> weakRefNoCreate = Ptr<WeakReferent>::New();
ptr->setWeakPtr(weakRefNoCreate);
weakRefNoCreate->setReferent(ptr.get());
}
mWeakPtr = ptr->mWeakPtr;
}
}
WeakPtr(const WeakPtr &other) : mWeakPtr(other.mWeakPtr) {}
template <typename U>
WeakPtr(const WeakPtr<U> &other) : mWeakPtr(other.mWeakPtr) {}
WeakPtr(WeakPtr &&other) noexcept : mWeakPtr(other.mWeakPtr) {}
~WeakPtr() { reset(); }
operator bool() const { return mWeakPtr && mWeakPtr->getReferent(); }
bool operator!() const {
bool ok = *this;
return !ok;
}
bool operator==(const WeakPtr &other) const {
return mWeakPtr == other.mWeakPtr;
}
bool operator!=(const WeakPtr &other) const {
return !(mWeakPtr != other.mWeakPtr);
}
bool operator==(const T *other) const { return lock().get() == other; }
bool operator==(T *other) const {
if (!mWeakPtr) {
return other == nullptr;
}
return mWeakPtr->getReferent() == other;
}
bool operator==(const Ptr<T> &other) const {
if (!mWeakPtr) {
return !other;
}
return mWeakPtr->getReferent() == other.get();
}
bool operator!=(const T *other) const {
bool equal = *this == other;
return !equal;
}
WeakPtr &operator=(const WeakPtr &other) {
this->mWeakPtr = other.mWeakPtr;
return *this;
}
Ptr<T> lock() const {
if (!mWeakPtr) {
return Ptr<T>();
}
T* out = static_cast<T*>(mWeakPtr->getReferent());
if (out->ref_count() == 0) {
// This is a static object, so the refcount is 0.
return Ptr<T>::NoTracking(*out);
}
// This is a heap object, so we need to ref it.
return Ptr<T>::TakeOwnership(static_cast<T *>(out));
}
bool expired() const {
if (!mWeakPtr) {
return true;
}
if (!mWeakPtr->getReferent()) {
return true;
}
return false;
}
void reset() {
if (mWeakPtr) {
mWeakPtr.reset();
}
}
Ptr<WeakReferent> mWeakPtr;
};
// Objects that inherit this class can be reference counted and put into
// a Ptr object. They can also be put into a WeakPtr object.
class Referent {
public:
virtual int ref_count() const;
protected:
Referent();
virtual ~Referent();
Referent(const Referent &);
Referent &operator=(const Referent &);
Referent(Referent &&);
Referent &operator=(Referent &&);
virtual void ref();
virtual void unref();
virtual void destroy();
private:
friend class WeakReferent;
template <typename T> friend class Ptr;
template <typename T> friend class WeakPtr;
void setWeakPtr(Ptr<WeakReferent> weakRefNoCreate) {
mWeakPtr = weakRefNoCreate;
}
mutable int mRefCount;
Ptr<WeakReferent> mWeakPtr; // Optional weak reference to this object.
};
template <typename T> inline WeakPtr<T> Ptr<T>::weakRefNoCreate() const {
if (!referent_) {
return WeakPtr<T>();
}
WeakReferent *tmp = get()->mWeakPtr.get();
if (!tmp) {
return WeakPtr<T>();
}
T *referent = static_cast<T *>(tmp->getReferent());
if (!referent) {
return WeakPtr<T>();
}
// At this point, we know that our weak referent is valid.
// However, the template parameter ensures that either we have
// an exact type, or are at least down-castable of it.
WeakPtr<T> out;
out.mWeakPtr = get()->mWeakPtr;
return out;
}
} // namespace fl

View File

@@ -0,0 +1,100 @@
#include "fl/rectangular_draw_buffer.h"
#include "fl/namespace.h"
#include "rgbw.h"
#include "fl/allocator.h"
namespace fl {
DrawItem::DrawItem(uint8_t pin, uint16_t numLeds, bool is_rgbw)
: mPin(pin), mIsRgbw(is_rgbw) {
if (is_rgbw) {
numLeds = Rgbw::size_as_rgb(numLeds);
}
mNumBytes = numLeds * 3;
}
Slice<uint8_t> RectangularDrawBuffer::getLedsBufferBytesForPin(uint8_t pin, bool clear_first) {
auto it = mPinToLedSegment.find(pin);
if (it == mPinToLedSegment.end()) {
FASTLED_ASSERT(false, "Pin not found in RectangularDrawBuffer");
return fl::Slice<uint8_t>();
}
fl::Slice<uint8_t> slice = it->second;
if (clear_first) {
memset(slice.data(), 0, slice.size() * sizeof(slice[0]));
}
return slice;
}
bool RectangularDrawBuffer::onQueuingStart() {
if (mQueueState == QUEUEING) {
return false;
}
mQueueState = QUEUEING;
mPinToLedSegment.clear();
mDrawList.swap(mPrevDrawList);
mDrawList.clear();
if (mAllLedsBufferUint8Size > 0) {
memset(mAllLedsBufferUint8.get(), 0, mAllLedsBufferUint8Size);
}
return true;
}
void RectangularDrawBuffer::queue(const DrawItem &item) {
mDrawList.push_back(item);
}
bool RectangularDrawBuffer::onQueuingDone() {
if (mQueueState == QUEUE_DONE) {
return false;
}
mQueueState = QUEUE_DONE;
mDrawListChangedThisFrame = mDrawList != mPrevDrawList;
// iterator through the current draw objects and calculate the total
// number of bytes (representing RGB or RGBW) that will be drawn this frame.
uint32_t total_bytes = 0;
uint32_t max_bytes_in_strip = 0;
uint32_t num_strips = 0;
getBlockInfo(&num_strips, &max_bytes_in_strip, &total_bytes);
if (total_bytes > mAllLedsBufferUint8Size) {
uint8_t* old_ptr = mAllLedsBufferUint8.release();
fl::LargeBlockAllocator<uint8_t>::Free(old_ptr);
uint8_t* ptr = fl::LargeBlockAllocator<uint8_t>::Alloc(total_bytes);
mAllLedsBufferUint8.reset(ptr);
}
mAllLedsBufferUint8Size = total_bytes;
uint32_t offset = 0;
for (auto it = mDrawList.begin(); it != mDrawList.end(); ++it) {
uint8_t pin = it->mPin;
Slice<uint8_t> slice(mAllLedsBufferUint8.get() + offset,
max_bytes_in_strip);
mPinToLedSegment[pin] = slice;
offset += max_bytes_in_strip;
}
return true;
}
uint32_t RectangularDrawBuffer::getMaxBytesInStrip() const {
uint32_t max_bytes = 0;
for (auto it = mDrawList.begin(); it != mDrawList.end(); ++it) {
max_bytes = MAX(max_bytes, it->mNumBytes);
}
return max_bytes;
}
uint32_t RectangularDrawBuffer::getTotalBytes() const {
uint32_t num_strips = mDrawList.size();
uint32_t max_bytes = getMaxBytesInStrip();
return num_strips * max_bytes;
}
void RectangularDrawBuffer::getBlockInfo(uint32_t *num_strips,
uint32_t *bytes_per_strip,
uint32_t *total_bytes) const {
*num_strips = mDrawList.size();
*bytes_per_strip = getMaxBytesInStrip();
*total_bytes = (*num_strips) * (*bytes_per_strip);
}
} // namespace fl

View File

@@ -0,0 +1,65 @@
#pragma once
#include <stdint.h>
#include "fl/map.h"
#include "fl/scoped_ptr.h"
#include "fl/slice.h"
#include "fl/vector.h"
#include "fl/namespace.h"
namespace fl {
struct DrawItem {
DrawItem() = default;
DrawItem(uint8_t pin, uint16_t numLeds, bool is_rgbw);
uint8_t mPin = 0;
uint32_t mNumBytes = 0;
bool mIsRgbw = false;
bool operator!=(const DrawItem &other) const {
return mPin != other.mPin || mNumBytes != other.mNumBytes ||
mIsRgbw != other.mIsRgbw;
}
};
// Needed by controllers that require a compact, rectangular buffer of pixel data.
// Namely, ObjectFLED and the I2S controllers.
// This class handles using multiple independent strips of LEDs, each with their own
// buffer of pixel data. The strips are not necessarily contiguous in memory.
// One or more DrawItems containing the pin number and number are queued
// up. When the queue-ing is done, the buffers are compacted into the rectangular buffer.
// Data access is achieved through a Slice<uint8_t> representing the pixel data for that pin.
class RectangularDrawBuffer {
public:
typedef fl::HeapVector<DrawItem> DrawList;
// We manually manage the memory for the buffer of all LEDs so that it can go
// into psram on ESP32S3, which is managed by fl::LargeBlockAllocator.
scoped_array<uint8_t> mAllLedsBufferUint8;
uint32_t mAllLedsBufferUint8Size = 0;
fl::FixedMap<uint8_t, fl::Slice<uint8_t>, 50> mPinToLedSegment;
DrawList mDrawList;
DrawList mPrevDrawList;
bool mDrawListChangedThisFrame = false;
enum QueueState { IDLE, QUEUEING, QUEUE_DONE };
QueueState mQueueState = IDLE;
RectangularDrawBuffer() = default;
~RectangularDrawBuffer() = default;
fl::Slice<uint8_t> getLedsBufferBytesForPin(uint8_t pin, bool clear_first=true);
// Safe to call multiple times before calling queue() once. Returns true on the first call, false after.
bool onQueuingStart();
void queue(const DrawItem &item);
// Safe to call multiple times before calling onQueueingStart() again. Returns true on the first call, false after.
bool onQueuingDone();
uint32_t getMaxBytesInStrip() const;
uint32_t getTotalBytes() const;
void getBlockInfo(uint32_t *num_strips, uint32_t *bytes_per_strip,
uint32_t *total_bytes) const;
};
} // namespace fl

30
FastLED/src/fl/ref.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include "fl/ptr.h"
#include "fl/namespace.h"
namespace fl {
Referent::Referent() : mRefCount(0) {}
Referent::~Referent() = default;
void Referent::ref() { mRefCount++; }
int Referent::ref_count() const { return mRefCount; }
void Referent::unref() {
if (--mRefCount == 0) {
if (mWeakPtr) {
mWeakPtr->setReferent(nullptr);
mWeakPtr.reset();
}
destroy();
}
}
void Referent::destroy() { delete this; }
Referent::Referent(const Referent &) = default;
Referent &Referent::operator=(const Referent &) = default;
Referent::Referent(Referent &&) = default;
Referent &Referent::operator=(Referent &&) = default;
} // namespace fl

15
FastLED/src/fl/register.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
/// @def FASTLED_REGISTER
/// Helper macro to replace the deprecated 'register' keyword if we're
/// using modern C++ where it's been removed entirely.
#if __cplusplus < 201703L
#define FASTLED_REGISTER register
#else
#ifdef FASTLED_REGISTER
#undef FASTLED_REGISTER
#endif
#define FASTLED_REGISTER
#endif

157
FastLED/src/fl/scoped_ptr.h Normal file
View File

@@ -0,0 +1,157 @@
#pragma once
#include <stddef.h>
#include "fl/namespace.h"
namespace fl {
template<typename T>
struct ArrayDeleter {
void operator()(T* ptr) {
delete[] ptr;
}
};
template<typename T>
struct PointerDeleter {
void operator()(T* ptr) {
delete ptr;
}
};
template <typename T, typename Deleter = PointerDeleter<T>>
class scoped_ptr {
public:
// Constructor
explicit scoped_ptr(T *ptr = nullptr, Deleter deleter = Deleter())
: ptr_(ptr), deleter_(deleter) {}
// Destructor
~scoped_ptr() { deleter_(ptr_); }
// Disable copy semantics (no copying allowed)
scoped_ptr(const scoped_ptr &) = delete;
scoped_ptr &operator=(const scoped_ptr &) = delete;
// Move constructor
scoped_ptr(scoped_ptr &&other) noexcept
: ptr_(other.ptr_), deleter_(other.deleter_) {
other.ptr_ = nullptr;
other.deleter_ = {};
}
// Move assignment operator
scoped_ptr &operator=(scoped_ptr &&other) noexcept {
if (this != &other) {
reset(other.ptr_);
deleter_ = other.deleter_;
other.ptr_ = nullptr;
other.deleter_ = {};
}
return *this;
}
// Access the managed object
T *operator->() const { return ptr_; }
// Dereference the managed object
T &operator*() const { return *ptr_; }
// Get the raw pointer
T *get() const { return ptr_; }
// Boolean conversion operator
explicit operator bool() const noexcept { return ptr_ != nullptr; }
// Logical NOT operator
bool operator!() const noexcept { return ptr_ == nullptr; }
// Release the managed object and reset the pointer
void reset(T *ptr = nullptr) {
if (ptr_ != ptr) {
deleter_(ptr_);
ptr_ = ptr;
}
}
T* release() {
T* tmp = ptr_;
ptr_ = nullptr;
return tmp;
}
private:
T *ptr_; // Managed pointer
Deleter deleter_; // Custom deleter
};
template <typename T, typename Deleter=ArrayDeleter<T>> class scoped_array {
public:
// Constructor
explicit scoped_array(T *arr = nullptr) : arr_(arr) {}
scoped_array(T *arr, Deleter deleter) : arr_(arr), deleter_(deleter) {}
// Destructor
~scoped_array() { deleter_(arr_); }
// Disable copy semantics (no copying allowed)
scoped_array(const scoped_array &) = delete;
scoped_array &operator=(const scoped_array &) = delete;
// Move constructor
scoped_array(scoped_array &&other) noexcept : arr_(other.arr_), deleter_(other.deleter_) {
other.arr_ = nullptr;
other.deleter_ = {};
}
// Move assignment operator
scoped_array &operator=(scoped_array &&other) noexcept {
if (this != &other) {
reset(other.arr_);
other.arr_ = nullptr;
}
return *this;
}
// Array subscript operator
T &operator[](size_t i) const { return arr_[i]; }
// Get the raw pointer
T *get() const { return arr_; }
// Boolean conversion operator
explicit operator bool() const noexcept { return arr_ != nullptr; }
// Logical NOT operator
bool operator!() const noexcept { return arr_ == nullptr; }
// Release the managed array and reset the pointer
void reset(T *arr = nullptr) {
if (arr_ == arr) {
return;
}
deleter_(arr_);
arr_ = arr;
}
void clear() {
reset();
}
T* release() {
T* tmp = arr_;
arr_ = nullptr;
return tmp;
}
private:
T *arr_; // Managed array pointer
Deleter deleter_ = {};
};
} // namespace fl

View File

@@ -0,0 +1,230 @@
/* Screenmap maps strip indexes to x,y coordinates. This is used for FastLED Web
* to map the 1D strip to a 2D grid. Note that the strip can have arbitrary
* size. this was first motivated during the (attempted? Oct. 19th 2024) port of
* the Chromancer project to FastLED Web.
*/
#include "fl/screenmap.h"
#include "fl/json.h"
#include "fl/map.h"
#include "fl/math_macros.h"
#include "fl/screenmap.h"
#include "fl/str.h"
#include "fl/vector.h"
#include "fl/warn.h"
#include "fl/namespace.h"
#include <math.h>
namespace fl {
ScreenMap ScreenMap::Circle(int numLeds, float cm_between_leds,
float cm_led_diameter) {
ScreenMap screenMap = ScreenMap(numLeds);
float circumference = numLeds * cm_between_leds;
float radius = circumference / (2 * PI);
for (int i = 0; i < numLeds; i++) {
float angle = i * 2 * PI / numLeds;
float x = radius * cos(angle) * 2;
float y = radius * sin(angle) * 2;
screenMap[i] = {x, y};
}
screenMap.setDiameter(cm_led_diameter);
return screenMap;
}
bool ScreenMap::ParseJson(const char *jsonStrScreenMap,
FixedMap<Str, ScreenMap, 16> *segmentMaps, Str *err) {
#if !FASTLED_ENABLE_JSON
if (err) {
*err = "JSON not enabled";
}
return false;
#else
JsonDocument doc;
Str _err;
if (!err) {
err = &_err;
}
bool ok = parseJson(jsonStrScreenMap, &doc, err);
if (!ok) {
FASTLED_WARN("Failed to parse json: " << err->c_str());
return false;
}
auto map = doc["map"];
for (auto kv : map.as<FLArduinoJson::JsonObject>()) {
auto segment = kv.value();
auto x = segment["x"];
auto y = segment["y"];
auto obj = segment["diameter"];
float diameter = -1.0f;
if (obj.is<float>()) {
float d = obj.as<float>();
if (d > 0.0f) {
diameter = d;
}
}
auto n = x.size();
ScreenMap segment_map(n, diameter);
for (uint16_t j = 0; j < n; j++) {
segment_map.set(j, pair_xy_float{x[j], y[j]});
}
segmentMaps->insert(kv.key().c_str(), segment_map);
}
return true;
#endif
}
bool ScreenMap::ParseJson(const char *jsonStrScreenMap,
const char *screenMapName, ScreenMap *screenmap,
Str *err) {
#if !FASTLED_ENABLE_JSON
if (err) {
*err = "JSON not enabled";
}
return false;
#else
FixedMap<Str, ScreenMap, 16> segmentMaps;
bool ok = ParseJson(jsonStrScreenMap, &segmentMaps, err);
if (!ok) {
return false;
}
if (segmentMaps.size() == 0) {
return false;
}
if (segmentMaps.has(screenMapName)) {
*screenmap = segmentMaps[screenMapName];
return true;
}
Str _err = "ScreenMap not found: ";
_err.append(screenMapName);
if (err) {
*err = _err;
}
FASTLED_WARN(_err.c_str());
return false;
#endif
}
void ScreenMap::toJson(const FixedMap<Str, ScreenMap, 16> &segmentMaps,
JsonDocument *_doc) {
#if !FASTLED_ENABLE_JSON
return;
#else
auto &doc = *_doc;
auto map = doc["map"].to<FLArduinoJson::JsonObject>();
for (auto kv : segmentMaps) {
auto segment = map[kv.first].to<FLArduinoJson::JsonObject>();
auto x_array = segment["x"].to<FLArduinoJson::JsonArray>();
auto y_array = segment["y"].to<FLArduinoJson::JsonArray>();
for (uint16_t i = 0; i < kv.second.getLength(); i++) {
const pair_xy_float &xy = kv.second[i];
x_array.add(xy.x);
y_array.add(xy.y);
}
float diameter = kv.second.getDiameter();
if (diameter < 0.0f) {
diameter = .5f; // 5mm.
}
if (diameter > 0.0f) {
segment["diameter"] = diameter;
}
}
#endif
}
void ScreenMap::toJsonStr(const FixedMap<Str, ScreenMap, 16> &segmentMaps,
Str *jsonBuffer) {
#if !FASTLED_ENABLE_JSON
return;
#else
JsonDocument doc;
toJson(segmentMaps, &doc);
fl::toJson(doc, jsonBuffer);
#endif
}
ScreenMap::ScreenMap(uint32_t length, float mDiameter)
: length(length), mDiameter(mDiameter) {
mLookUpTable = LUTXYFLOATPtr::New(length);
LUTXYFLOAT &lut = *mLookUpTable.get();
pair_xy_float *data = lut.getData();
for (uint32_t x = 0; x < length; x++) {
data[x] = {0, 0};
}
}
ScreenMap::ScreenMap(const pair_xy_float *lut, uint32_t length, float diameter)
: length(length), mDiameter(diameter) {
mLookUpTable = LUTXYFLOATPtr::New(length);
LUTXYFLOAT &lut16xy = *mLookUpTable.get();
pair_xy_float *data = lut16xy.getData();
for (uint32_t x = 0; x < length; x++) {
data[x] = lut[x];
}
}
ScreenMap::ScreenMap(const ScreenMap &other) {
mDiameter = other.mDiameter;
length = other.length;
mLookUpTable = other.mLookUpTable;
}
void ScreenMap::set(uint16_t index, const pair_xy_float &p) {
if (mLookUpTable) {
LUTXYFLOAT &lut = *mLookUpTable.get();
auto *data = lut.getData();
data[index] = p;
}
}
void ScreenMap::setDiameter(float diameter) { mDiameter = diameter; }
pair_xy_float ScreenMap::mapToIndex(uint32_t x) const {
if (x >= length || !mLookUpTable) {
return {0, 0};
}
LUTXYFLOAT &lut = *mLookUpTable.get();
pair_xy_float screen_coords = lut[x];
return screen_coords;
}
uint32_t ScreenMap::getLength() const { return length; }
float ScreenMap::getDiameter() const { return mDiameter; }
const pair_xy_float &ScreenMap::empty() {
static const pair_xy_float s_empty = pair_xy_float(0, 0);
return s_empty;
}
const pair_xy_float &ScreenMap::operator[](uint32_t x) const {
if (x >= length || !mLookUpTable) {
return empty(); // better than crashing.
}
LUTXYFLOAT &lut = *mLookUpTable.get();
return lut[x];
}
pair_xy_float &ScreenMap::operator[](uint32_t x) {
if (x >= length || !mLookUpTable) {
return const_cast<pair_xy_float &>(empty()); // better than crashing.
}
LUTXYFLOAT &lut = *mLookUpTable.get();
auto *data = lut.getData();
return data[x];
}
ScreenMap &ScreenMap::operator=(const ScreenMap &other) {
if (this != &other) {
mDiameter = other.mDiameter;
length = other.length;
mLookUpTable = other.mLookUpTable;
}
return *this;
}
} // namespace fl

View File

@@ -0,0 +1,86 @@
#pragma once
#include <stdint.h>
#include "fl/force_inline.h"
#include "fl/lut.h"
#include "fl/ptr.h"
#include "fl/map.h"
#include "fl/str.h"
#include "fl/namespace.h"
/* Screenmap maps strip indexes to x,y coordinates. This is used for FastLED Web
* to map the 1D strip to a 2D screen. Note that the strip can have arbitrary
* size. this was first motivated by the effort to port theChromancer project to
* FastLED for the browser.
*/
namespace fl {
class Str;
class JsonDocument;
// ScreenMap screen map maps strip indexes to x,y coordinates for a ui
// canvas in float format.
// This class is cheap to copy as it uses smart pointers for shared data.
class ScreenMap {
public:
static ScreenMap Circle(int numLeds, float cm_between_leds = 1.5f,
float cm_led_diameter = 0.5f);
ScreenMap() = default;
// is_reverse is false by default for linear layout
ScreenMap(uint32_t length, float mDiameter = -1.0f);
ScreenMap(const pair_xy_float *lut, uint32_t length,
float diameter = -1.0);
template <uint32_t N>
ScreenMap(const pair_xy_float (&lut)[N], float diameter = -1.0)
: ScreenMap(lut, N, diameter) {}
ScreenMap(const ScreenMap &other);
const pair_xy_float &operator[](uint32_t x) const;
void set(uint16_t index, const pair_xy_float &p);
pair_xy_float &operator[](uint32_t x);
// TODO: change this name to setDiameterLed. Default should be .5f
// for 5 mm ws lense.
void setDiameter(float diameter);
// define the assignment operator
ScreenMap &operator=(const ScreenMap &other);
pair_xy_float mapToIndex(uint32_t x) const;
uint32_t getLength() const;
// The diameter each point represents.
float getDiameter() const;
static bool ParseJson(const char *jsonStrScreenMap,
FixedMap<Str, ScreenMap, 16> *segmentMaps,
Str *err = nullptr);
static bool ParseJson(const char *jsonStrScreenMap,
const char *screenMapName, ScreenMap *screenmap,
Str *err = nullptr);
static void toJsonStr(const FixedMap<Str, ScreenMap, 16> &,
Str *jsonBuffer);
static void toJson(const FixedMap<Str, ScreenMap, 16> &,
JsonDocument *doc);
private:
static const pair_xy_float &empty();
uint32_t length = 0;
float mDiameter = -1.0f; // Only serialized if it's not > 0.0f.
LUTXYFLOATPtr mLookUpTable;
};
} // namespace fl

153
FastLED/src/fl/set.h Normal file
View File

@@ -0,0 +1,153 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "fl/namespace.h"
#include "fl/vector.h"
namespace fl {
// A simple unordered set implementation with a fixed size.
// The user is responsible for making sure that the inserts
// do not exceed the capacity of the set, otherwise they will
// fail. Because of this limitation, this set is not a drop in
// replacement for std::set.
template<typename Key, size_t N>
class FixedSet {
public:
typedef FixedVector<Key, N> VectorType;
typedef typename VectorType::iterator iterator;
typedef typename VectorType::const_iterator const_iterator;
// Constructor
constexpr FixedSet() = default;
iterator begin() {
return data.begin();
}
iterator end() {
return data.end();
}
const_iterator begin() const {
return data.begin();
}
const_iterator end() const {
return data.end();
}
iterator find(const Key& key) {
for (auto it = begin(); it != end(); ++it) {
if (*it == key) {
return it;
}
}
return end();
}
const_iterator find(const Key& key) const {
for (auto it = begin(); it != end(); ++it) {
if (*it == key) {
return it;
}
}
return end();
}
bool insert(const Key& key) {
if (data.size() < N) {
auto it = find(key);
if (it == end()) {
data.push_back(key);
return true;
}
}
return false;
}
bool erase(const Key& key) {
auto it = find(key);
if (it != end()) {
data.erase(it);
return true;
}
return false;
}
bool erase(iterator pos) {
if (pos != end()) {
data.erase(pos);
return true;
}
return false;
}
bool next(const Key& key, Key* next_key, bool allow_rollover = false) const {
const_iterator it = find(key);
if (it != end()) {
++it;
if (it != end()) {
*next_key = *it;
return true;
} else if (allow_rollover && !empty()) {
*next_key = *begin();
return true;
}
}
return false;
}
bool prev(const Key& key, Key* prev_key, bool allow_rollover = false) const {
const_iterator it = find(key);
if (it != end()) {
if (it != begin()) {
--it;
*prev_key = *it;
return true;
} else if (allow_rollover && !empty()) {
*prev_key = data[data.size() - 1];
return true;
}
}
return false;
}
// Get the current size of the set
constexpr size_t size() const {
return data.size();
}
constexpr bool empty() const {
return data.empty();
}
// Get the capacity of the set
constexpr size_t capacity() const {
return N;
}
// Clear the set
void clear() {
data.clear();
}
bool has(const Key& key) const {
return find(key) != end();
}
// Return the first element of the set
const Key& front() const {
return data.front();
}
// Return the last element of the set
const Key& back() const {
return data.back();
}
private:
VectorType data;
};
} // namespace fl

35
FastLED/src/fl/sin32.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include <stdint.h>
namespace fl {
const int16_t sinLut[] =
{
0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793,
12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594,
23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956,
30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757,
32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571,
30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790, 26319, 25832, 25329, 24811, 24279, 23731,
23170, 22594, 22005, 21403, 20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, 15446, 14732, 14010, 13279,
12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179, 6393, 5602, 4808, 4011, 3212, 2410, 1608, 804,
0, -804, -1608, -2410, -3212, -4011, -4808, -5602, -6393, -7179, -7962, -8739, -9512, -10278, -11039, -11793,
-12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, -18204, -18868, -19519, -20159, -20787, -21403, -22005, -22594,
-23170, -23731, -24279, -24811, -25329, -25832, -26319, -26790, -27245, -27683, -28105, -28510, -28898, -29268, -29621, -29956,
-30273, -30571, -30852, -31113, -31356, -31580, -31785, -31971, -32137, -32285, -32412, -32521, -32609, -32678, -32728, -32757,
-32767, -32757, -32728, -32678, -32609, -32521, -32412, -32285, -32137, -31971, -31785, -31580, -31356, -31113, -30852, -30571,
-30273, -29956, -29621, -29268, -28898, -28510, -28105, -27683, -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731,
-23170, -22594, -22005, -21403, -20787, -20159, -19519, -18868, -18204, -17530, -16846, -16151, -15446, -14732, -14010, -13279,
-12539, -11793, -11039, -10278, -9512, -8739, -7962, -7179, -6393, -5602, -4808, -4011, -3212, -2410, -1608, -804,
0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793,
12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594,
23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956,
30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757,
32767};
const int16_t *sinArray = &sinLut[0];
const int16_t *cosArray = &sinLut[64];
} // namespace fl

70
FastLED/src/fl/sin32.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include <stdint.h>
#include "namespace.h"
#include "force_inline.h"
namespace fl {
// fast and accurate sin and cos approximations
// they differ only 0.01 % from actual sinf and cosf funtions
// sin32 and cos32 use 24 bit unsigned integer arguments
// 0 to 16777216 is one cycle
// they output an integer between -2147418112 and 2147418112
// that's 32767 * 65536
// this is because I use int16_t look up table to interpolate the results
//
// sin16 and cos16 are faster and more accurate funtions that take uint16_t as arguments
// and return int16_t as output
// they can replace the older implementation and are 62 times more accurate and twice as fast
// the downside with these funtions is that they use 640 bytes for the look up table
// thats a lot for old microcontrollers but nothing for modern ones
//
// sin32 and cos32 take about 13 cyces to execute on an esp32
// they're 32 times faster than sinf and cosf
// you can use choose to use these new by writing
// #define USE_SIN_32 before #include "FastLED.h"
extern const int16_t *sinArray;
extern const int16_t *cosArray;
// 0 to 16777216 is a full circle
// output is between -2147418112 and 2147418112
FASTLED_FORCE_INLINE static int32_t sin32(uint32_t angle)
{
uint8_t angle256 = angle / 65536;
int32_t subAngle = angle % 65536;
return sinArray[angle256] * (65536 - subAngle) + sinArray[angle256 + 1] * subAngle;
}
// 0 to 16777216 is a full circle
// output is between -2147418112 and 2147418112
FASTLED_FORCE_INLINE static int32_t cos32(uint32_t angle)
{
uint8_t angle256 = angle / 65536;
int32_t subAngle = angle % 65536;
return cosArray[angle256] * (65536 - subAngle) + cosArray[angle256 + 1] * subAngle;
}
// 0 to 65536 is a full circle
// output is between -32767 and 32767
FASTLED_FORCE_INLINE static int16_t sin16lut(uint16_t angle)
{
uint8_t angle256 = angle / 256;
int32_t subAngle = angle % 256;
return (sinArray[angle256] * (256 - subAngle) + sinArray[angle256 + 1] * subAngle) / 256;
}
// 0 to 65536 is a full circle
// output is between -32767 and 32767
FASTLED_FORCE_INLINE static int16_t cos16lut(uint16_t angle)
{
uint8_t angle256 = angle / 256;
int32_t subAngle = angle % 256;
return (cosArray[angle256] * (256 - subAngle) + cosArray[angle256 + 1] * subAngle) / 256;
}
} // namespace fl

View File

@@ -0,0 +1,25 @@
#pragma once
#include "fl/namespace.h"
namespace fl {
// A templated singleton class, parameterized by the type of the singleton and an optional integer.
template<typename T, int N = 0>
class Singleton {
public:
static T& instance() {
static T instance;
return instance;
}
static T* instanceRef() {
return &instance();
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
} // namespace fl

129
FastLED/src/fl/slice.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace fl {
// Slice<int> is equivalent to int* with a length. It is used to pass around
// arrays of integers with a length, without needing to pass around a separate
// length parameter.
// It works just like an array of objects, but it also knows its length.
template<typename T>
class Slice {
public:
Slice() : mData(nullptr), mSize(0) {}
Slice(T* data, size_t size) : mData(data), mSize(size) {}
Slice(const Slice& other) : mData(other.mData), mSize(other.mSize) {}
Slice& operator=(const Slice& other) {
mData = other.mData;
mSize = other.mSize;
return *this;
}
// Automatic promotion to const Slice<const T>
operator Slice<const T>() const {
return *this;
}
T& operator[](size_t index) {
// No bounds checking in embedded environment
return mData[index];
}
const T& operator[](size_t index) const {
// No bounds checking in embedded environment
return mData[index];
}
T* begin() const {
return mData;
}
T* end() const {
return mData + mSize;
}
size_t length() const {
return mSize;
}
const T* data() const {
return mData;
}
T* data() {
return mData;
}
size_t size() const {
return mSize;
}
Slice<T> slice(size_t start, size_t end) const {
// No bounds checking in embedded environment
return Slice<T>(mData + start, end - start);
}
Slice<T> slice(size_t start) const {
// No bounds checking in embedded environment
return Slice<T>(mData + start, mSize - start);
}
// Find the first occurrence of a value in the slice
// Returns the index of the first occurrence if found, or size_t(-1) if not found
size_t find(const T& value) const {
for (size_t i = 0; i < mSize; ++i) {
if (mData[i] == value) {
return i;
}
}
return size_t(-1);
}
bool pop_front() {
if (mSize == 0) {
return false;
}
++mData;
--mSize;
return true;
}
bool pop_back() {
if (mSize == 0) {
return false;
}
--mSize;
return true;
}
T& front() {
return *mData;
}
const T& front() const {
return *mData;
}
T& back() {
return *(mData + mSize - 1);
}
const T& back() const {
return *(mData + mSize - 1);
}
bool empty() {
return mSize == 0;
}
private:
T* mData;
size_t mSize;
};
} // namespace fl

171
FastLED/src/fl/str.cpp Normal file
View File

@@ -0,0 +1,171 @@
#include <stdlib.h>
#include "fl/str.h"
#include "fl/namespace.h"
namespace fl {
namespace string_functions {
static int itoa(int value, char *sp, int radix) {
char tmp[16]; // be careful with the length of the buffer
char *tp = tmp;
int i;
unsigned v;
int sign = (radix == 10 && value < 0);
if (sign)
v = -value;
else
v = (unsigned)value;
while (v || tp == tmp) {
i = v % radix;
v = radix ? v / radix : 0;
if (i < 10)
*tp++ = i + '0';
else
*tp++ = i + 'a' - 10;
}
int len = tp - tmp;
if (sign) {
*sp++ = '-';
len++;
}
while (tp > tmp)
*sp++ = *--tp;
return len;
}
static float atoff(const char *str, size_t len) {
float result = 0.0f; // The resulting number
float sign = 1.0f; // Positive or negative
float fraction = 0.0f; // Fractional part
float divisor = 1.0f; // Divisor for the fractional part
int isFractional = 0; // Whether the current part is fractional
size_t pos = 0; // Current position in the string
// Handle empty input
if (len == 0) {
return 0.0f;
}
// Skip leading whitespace (manual check instead of isspace)
while (pos < len && (str[pos] == ' ' || str[pos] == '\t' || str[pos] == '\n' || str[pos] == '\r' || str[pos] == '\f' || str[pos] == '\v')) {
pos++;
}
// Handle optional sign
if (pos < len && str[pos] == '-') {
sign = -1.0f;
pos++;
} else if (pos < len && str[pos] == '+') {
pos++;
}
// Main parsing loop
while (pos < len) {
if (str[pos] >= '0' && str[pos] <= '9') {
if (isFractional) {
divisor *= 10.0f;
fraction += (str[pos] - '0') / divisor;
} else {
result = result * 10.0f + (str[pos] - '0');
}
} else if (str[pos] == '.' && !isFractional) {
isFractional = 1;
} else {
// Stop parsing at invalid characters
break;
}
pos++;
}
// Combine integer and fractional parts
result = result + fraction;
// Apply the sign
return sign * result;
}
} // namespace string_functions
void StringFormatter::append(int32_t val, StrN<64> *dst) {
char buf[63] = {0};
string_functions::itoa(val, buf, 10);
dst->write(buf, strlen(buf));
}
StringHolder::StringHolder(const char *str) {
mLength = strlen(str); // Don't include null terminator in length
mCapacity = mLength + 1; // Capacity includes null terminator
mData = new char[mCapacity];
memcpy(mData, str, mLength);
mData[mLength] = '\0';
}
StringHolder::StringHolder(size_t length) {
mData = (char *)malloc(length + 1);
if (mData) {
mLength = length;
mData[mLength] = '\0';
} else {
mLength = 0;
}
mCapacity = mLength;
}
StringHolder::StringHolder(const char *str, size_t length) {
mData = (char *)malloc(length + 1);
if (mData) {
mLength = length;
memcpy(mData, str, mLength);
mData[mLength] = '\0';
} else {
mLength = 0;
}
mCapacity = mLength;
}
StringHolder::~StringHolder() {
free(mData); // Release the memory
}
void StringHolder::grow(size_t newLength) {
if (newLength <= mCapacity) {
// New length must be greater than current length
mLength = newLength;
return;
}
char *newData = (char *)realloc(mData, newLength + 1);
if (newData) {
mData = newData;
mLength = newLength;
mCapacity = newLength;
mData[mLength] = '\0'; // Ensure null-termination
} else {
// handle re-allocation failure.
char *newData = (char *)malloc(newLength + 1);
if (newData) {
memcpy(newData, mData, mLength + 1);
free(mData);
mData = newData;
mLength = newLength;
mCapacity = mLength;
} else {
// memory failure.
}
}
}
float StringFormatter::parseFloat(const char *str, size_t len) {
return string_functions::atoff(str, len);
}
}

443
FastLED/src/fl/str.h Normal file
View File

@@ -0,0 +1,443 @@
#pragma once
#include <string.h>
#include <stdint.h>
#include "fl/ptr.h"
#include "fl/template_magic.h"
#include "fl/vector.h"
#include "fl/namespace.h"
#include "fl/math_macros.h"
#ifndef FASTLED_STR_INLINED_SIZE
#define FASTLED_STR_INLINED_SIZE 64
#endif
namespace fl { // Mandatory namespace for this class since it has name collisions.
template <size_t N> class StrN;
// A copy on write string class. Fast to copy from another
// Str object as read only pointers are shared. If the size
// of the string is below FASTLED_STR_INLINED_SIZE then the
// the entire string fits in the object and no heap allocation occurs.
// When the string is large enough it will overflow into heap
// allocated memory. Copy on write means that if the Str has
// a heap buffer that is shared with another Str object, then
// a copy is made and then modified in place.
// If write() or append() is called then the internal data structure
// will grow to accomodate the new data with extra space for future,
// like a vector.
class Str;
///////////////////////////////////////////////////////
// Implementation details.
FASTLED_SMART_PTR(StringHolder);
class StringFormatter {
public:
static void append(int32_t val, StrN<64> *dst);
static bool isSpace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }
static float parseFloat(const char *str, size_t len);
static bool isDigit(char c) { return c >= '0' && c <= '9'; }
};
class StringHolder : public fl::Referent {
public:
StringHolder(const char *str);
StringHolder(size_t length);
StringHolder(const char *str, size_t length);
StringHolder(const StringHolder &other) = delete;
StringHolder &operator=(const StringHolder &other) = delete;
~StringHolder();
bool isShared() const { return ref_count() > 1; }
void grow(size_t newLength);
bool hasCapacity(size_t newLength) const { return newLength <= mCapacity; }
const char *data() const { return mData; }
char *data() { return mData; }
size_t length() const { return mLength; }
size_t capacity() const { return mCapacity; }
bool copy(const char *str, size_t len) {
if ((len+1) > mCapacity) {
return false;
}
memcpy(mData, str, len);
mData[len] = '\0';
mLength = len;
return true;
}
private:
char *mData = nullptr;
size_t mLength = 0;
size_t mCapacity = 0;
};
template <size_t SIZE = 64> class StrN {
private:
size_t mLength = 0;
char mInlineData[SIZE] = {0};
StringHolderPtr mHeapData;
public:
// Constructors
StrN() = default;
// cppcheck-suppress-begin [operatorEqVarError]
template <size_t M> StrN(const StrN<M> &other) { copy(other); }
StrN(const char *str) {
size_t len = strlen(str);
mLength = len; // Length is without null terminator
if (len + 1 <= SIZE) { // Check capacity including null
memcpy(mInlineData, str, len + 1); // Copy including null
mHeapData.reset();
} else {
mHeapData = StringHolderPtr::New(str);
}
}
StrN(const StrN &other) { copy(other); }
void copy(const char *str) {
size_t len = strlen(str);
mLength = len;
if (len + 1 <= SIZE) {
memcpy(mInlineData, str, len + 1);
mHeapData.reset();
} else {
if (mHeapData && !mHeapData->isShared()) {
// We are the sole owners of this data so we can modify it
mHeapData->copy(str, len);
return;
}
mHeapData.reset();
mHeapData = StringHolderPtr::New(str);
}
}
template<int N> StrN(const char (&str)[N]) {
copy(str, N-1); // Subtract 1 to not count null terminator
}
template<int N> StrN &operator=(const char (&str)[N]) {
assign(str, N);
return *this;
}
StrN &operator=(const StrN &other) {
copy(other);
return *this;
}
template <size_t M> StrN &operator=(const StrN<M> &other) {
copy(other);
return *this;
}
// cppcheck-suppress-end
bool operator==(const StrN &other) const {
return strcmp(c_str(), other.c_str()) == 0;
}
bool operator!=(const StrN &other) const {
return strcmp(c_str(), other.c_str()) != 0;
}
void copy(const char* str, size_t len) {
mLength = len;
if (len + 1 <= SIZE) {
memcpy(mInlineData, str, len + 1);
mHeapData.reset();
} else {
mHeapData = StringHolderPtr::New(str, len);
}
}
template <size_t M> void copy(const StrN<M> &other) {
size_t len = other.size();
if (len + 1 <= SIZE) {
memcpy(mInlineData, other.c_str(), len + 1);
mHeapData.reset();
} else {
if (other.mHeapData) {
mHeapData = other.mHeapData;
} else {
mHeapData = StringHolderPtr::New(other.c_str());
}
}
mLength = len;
}
size_t capacity() const {
return mHeapData ? mHeapData->capacity() : SIZE;
}
size_t write(const uint8_t *data, size_t n) {
const char *str = reinterpret_cast<const char *>(data);
return write(str, n);
}
size_t write(const char *str, size_t n) {
size_t newLen = mLength + n;
if (mHeapData && !mHeapData->isShared()) {
if (!mHeapData->hasCapacity(newLen)) {
size_t grow_length = MAX(3, newLen * 3 / 2);
mHeapData->grow(grow_length); // Grow by 50%
}
memcpy(mHeapData->data() + mLength, str, n);
mLength = newLen;
mHeapData->data()[mLength] = '\0';
return mLength;
}
if (newLen + 1 <= SIZE) {
memcpy(mInlineData + mLength, str, n);
mLength = newLen;
mInlineData[mLength] = '\0';
return mLength;
}
mHeapData.reset();
StringHolderPtr newData = StringHolderPtr::New(newLen);
if (newData) {
memcpy(newData->data(), c_str(), mLength);
memcpy(newData->data() + mLength, str, n);
newData->data()[newLen] = '\0';
mHeapData = newData;
mLength = newLen;
}
mHeapData = newData;
return mLength;
}
size_t write(char c) { return write(&c, 1); }
size_t write(uint8_t c) {
const char *str = reinterpret_cast<const char *>(&c);
return write(str, 1);
}
size_t write(const uint16_t& n) {
StrN<64> dst;
StringFormatter::append(n, &dst); // Inlined size should suffice
return write(dst.c_str(), dst.size());
}
size_t write(const uint32_t& val) {
StrN<64> dst;
StringFormatter::append(val, &dst); // Inlined size should suffice
return write(dst.c_str(), dst.size());
}
size_t write(const int32_t& val) {
StrN<64> dst;
StringFormatter::append(val, &dst); // Inlined size should suffice
return write(dst.c_str(), dst.size());
}
size_t write(const int8_t val) {
StrN<64> dst;
StringFormatter::append(int16_t(val), &dst); // Inlined size should suffice
return write(dst.c_str(), dst.size());
}
// Destructor
~StrN() {}
// Accessors
size_t size() const { return mLength; }
size_t length() const { return size(); }
const char *c_str() const {
return mHeapData ? mHeapData->data() : mInlineData;
}
char *c_str_mutable() {
return mHeapData ? mHeapData->data() : mInlineData;
}
char &operator[](size_t index) {
if (index >= mLength) {
static char dummy = '\0';
return dummy;
}
return c_str_mutable()[index];
}
const char &operator[](size_t index) const {
if (index >= mLength) {
static char dummy = '\0';
return dummy;
}
return c_str()[index];
}
bool empty() const { return mLength == 0; }
// Append method
bool operator<(const StrN &other) const {
return strcmp(c_str(), other.c_str()) < 0;
}
template<size_t M> bool operator<(const StrN<M> &other) const {
return strcmp(c_str(), other.c_str()) < 0;
}
void reserve(size_t newCapacity) {
// If capacity is less than current length, do nothing
if (newCapacity <= mLength) {
return;
}
// If new capacity fits in inline buffer, no need to allocate
if (newCapacity + 1 <= SIZE) {
return;
}
// If we already have unshared heap data with sufficient capacity, do nothing
if (mHeapData && !mHeapData->isShared() && mHeapData->hasCapacity(newCapacity)) {
return;
}
// Need to allocate new storage
StringHolderPtr newData = StringHolderPtr::New(newCapacity);
if (newData) {
// Copy existing content
memcpy(newData->data(), c_str(), mLength);
newData->data()[mLength] = '\0';
mHeapData = newData;
}
}
void clear(bool freeMemory = false) {
mLength = 0;
if (freeMemory && mHeapData) {
mHeapData.reset();
}
}
int16_t find(const char &value) const {
for (size_t i = 0; i < mLength; ++i) {
if (c_str()[i] == value) {
return i;
}
}
return -1;
}
StrN substring(size_t start, size_t end) const {
if (start >= mLength) {
return StrN();
}
if (end > mLength) {
end = mLength;
}
if (start >= end) {
return StrN();
}
StrN out;
out.copy(c_str() + start, end - start);
return out;
}
StrN trim() const {
StrN out;
size_t start = 0;
size_t end = mLength;
while (start < mLength && StringFormatter::isSpace(c_str()[start])) {
start++;
}
while (end > start && StringFormatter::isSpace(c_str()[end - 1])) {
end--;
}
return substring(start, end);
}
float toFloat() const {
return StringFormatter::parseFloat(c_str(), mLength);
}
private:
StringHolderPtr mData;
};
class Str : public StrN<FASTLED_STR_INLINED_SIZE> {
public:
Str() : StrN<FASTLED_STR_INLINED_SIZE>() {}
Str(const char *str) : StrN<FASTLED_STR_INLINED_SIZE>(str) {}
Str(const Str &other) : StrN<FASTLED_STR_INLINED_SIZE>(other) {}
template <size_t M>
Str(const StrN<M> &other) : StrN<FASTLED_STR_INLINED_SIZE>(other) {}
Str &operator=(const Str &other) {
copy(other);
return *this;
}
bool operator>(const Str &other) const {
return strcmp(c_str(), other.c_str()) > 0;
}
bool operator>=(const Str &other) const {
return strcmp(c_str(), other.c_str()) >= 0;
}
bool operator<(const Str &other) const {
return strcmp(c_str(), other.c_str()) < 0;
}
bool operator<=(const Str &other) const {
return strcmp(c_str(), other.c_str()) <= 0;
}
bool operator==(const Str &other) const {
return strcmp(c_str(), other.c_str()) == 0;
}
bool operator!=(const Str &other) const {
return strcmp(c_str(), other.c_str()) != 0;
}
Str& operator+=(const Str &other) {
append(other.c_str(), other.size());
return *this;
}
Str& append(const char *str) { write(str, strlen(str)); return *this; }
Str& append(const char *str, size_t len) { write(str, len); return *this; }
//Str& append(char c) { write(&c, 1); return *this; }
Str& append(const int8_t& c) {
const char* str = reinterpret_cast<const char*>(&c);
write(str, 1); return *this;
}
Str& append(const uint8_t& c) { write(uint16_t(c)); return *this; }
Str& append(const uint16_t& val) { write(val); return *this; }
Str& append(const int16_t& val) { write(uint32_t(val)); return *this; }
Str& append(const uint32_t& val) { write(val); return *this; }
Str& append(const int32_t& c) { write(c); return *this; }
Str& append(const float& val) {
int32_t i = static_cast<int32_t>(val * 100);
// append the integer part
append(i / 100);
append(".");
// append the decimal part
append(i % 100);
return *this;
}
Str& append(const double& val) {
return append(float(val));
}
Str& append(const StrN &str) { write(str.c_str(), str.size()); return *this; }
};
} // namespace fl

208
FastLED/src/fl/strstream.h Normal file
View File

@@ -0,0 +1,208 @@
#pragma once
#include "crgb.h"
#include "str.h"
#ifndef FASTLED_STRSTREAM_USES_SIZE_T
#if defined(__AVR__) || defined(ESP8266) || defined(ESP32)
#define FASTLED_STRSTREAM_USES_SIZE_T 0
#else
#define FASTLED_STRSTREAM_USES_SIZE_T 1
#endif
#endif
namespace fl {
template <typename T> struct StrStreamHelper {
static void append(Str &str, const T& n) { str.append(n); }
};
template <> struct StrStreamHelper<int> {
static void append(Str &str, const int& n) { str.append(int32_t(n)); }
};
template <> struct StrStreamHelper<uint8_t> {
static void append(Str &str, const uint8_t& n) { str.append(uint16_t(n)); }
};
template <> struct StrStreamHelper<char> {
static void append(Str &str, const char& n) { str.append(uint16_t(n)); }
};
template <> struct StrStreamHelper<unsigned int> {
static void append(Str &str, const unsigned int& n) { str.append(uint32_t(n)); }
};
class StrStream {
public:
StrStream() = default;
StrStream(const Str &str) : mStr(str) {}
void setTreatCharAsInt(bool treatCharAsInt) {
mTreatCharAsInt = treatCharAsInt;
}
const Str &str() const { return mStr; }
const char *c_str() const { return mStr.c_str(); }
StrStream &operator<<(const CRGB &rgb) {
mStr.append("CRGB(");
mStr.append(rgb.r);
mStr.append(",");
mStr.append(rgb.g);
mStr.append(",");
mStr.append(rgb.b);
mStr.append(")");
return *this;
}
StrStream &operator=(uint16_t n) {
mStr.clear();
(*this) << n;
return *this;
}
StrStream &operator=(uint8_t n) {
mStr.clear();
(*this) << n;
return *this;
}
StrStream &operator=(char c) {
mStr.clear();
(*this) << c;
return *this;
}
// << operator section
StrStream &operator<<(const Str &str) {
mStr.append(str);
return *this;
}
StrStream &operator<<(const char *str) {
mStr.append(str);
return *this;
}
StrStream& operator<<(const float& f) {
// multiply by 100 and round to get 2 decimal places
mStr.append(f);
return *this;
}
StrStream& operator<<(const double& f) {
// multiply by 100 and round to get 2 decimal places
mStr.append(f);
return *this;
}
StrStream &operator<<(char c) {
if (mTreatCharAsInt) {
StrStreamHelper<int>::append(mStr, c);
} else {
StrStreamHelper<char>::append(mStr, c);
}
return *this;
}
#if FASTLED_STRSTREAM_USES_SIZE_T
StrStream &operator<<(size_t n) {
mStr.append(uint32_t(n));
return *this;
}
#endif
template<typename T>
StrStream &operator<<(T n) {
StrStreamHelper<T>::append(mStr, n);
return *this;
}
StrStream &operator<<(uint8_t n) {
if (mTreatCharAsInt) {
mStr.append(uint16_t(n));
} else {
mStr.append(n);
}
return *this;
}
StrStream &operator<<(uint16_t n) {
mStr.append(n);
return *this;
}
StrStream &operator<<(int16_t n) {
mStr.append(n);
return *this;
}
StrStream &operator<<(uint32_t n) {
mStr.append(uint32_t(n));
return *this;
}
StrStream &operator<<(int32_t n) {
mStr.append(n);
return *this;
}
// assignment operator completely replaces the current string
StrStream &operator=(const Str &str) {
mStr = str;
return *this;
}
StrStream &operator=(const char *str) {
mStr = str;
return *this;
}
// crgb
StrStream &operator=(const CRGB &rgb) {
mStr.clear();
(*this) << rgb;
return *this;
}
private:
Str mStr;
bool mTreatCharAsInt = true;
};
class FakeStrStream {
public:
template<typename T>
FakeStrStream& operator<<(const T&) { return *this; }
FakeStrStream& operator<<(const char*) { return *this; }
template<typename T>
FakeStrStream& operator=(const T&) { return *this; }
FakeStrStream& operator<<(const CRGB&) { return *this; }
FakeStrStream& operator<<(const Str&) { return *this; }
FakeStrStream& operator<<(char) { return *this; }
#if FASTLED_STRSTREAM_USES_SIZE_T
FakeStrStream& operator<<(size_t) { return *this; }
#endif
FakeStrStream& operator<<(uint8_t) { return *this; }
FakeStrStream& operator<<(uint16_t) { return *this; }
FakeStrStream& operator<<(int16_t) { return *this; }
FakeStrStream& operator<<(uint32_t) { return *this; }
FakeStrStream& operator<<(int32_t) { return *this; }
FakeStrStream& operator=(const Str&) { return *this; }
FakeStrStream& operator=(const CRGB&) { return *this; }
FakeStrStream& operator=(uint16_t) { return *this; }
FakeStrStream& operator=(uint8_t) { return *this; }
FakeStrStream& operator=(char) { return *this; }
};
} // namespace fl

View File

@@ -0,0 +1,36 @@
/*
* This is a stub implementation of main that can be used to include an *.ino
* file which is so close to C++ that many of them can be compiled as C++. The
* notable difference between a *.ino file and a *.cpp file is that the *.ino
* file does not need to include function prototypes, and are instead
* auto-generated.
*/
// This can't be in the namespace fl. It needs to be in the global namespace.
#if defined(FASTLED_STUB_MAIN) || defined(FASTLED_STUB_MAIN_INCLUDE_INO)
#ifndef _FASTLED_STRINGIFY
#define _FASTLED_STRINGIFY_HELPER(x) #x
#define _FASTLED_STRINGIFY(x) _FASTLED_STRINGIFY_HELPER(x)
#endif
#ifdef FASTLED_STUB_MAIN_INCLUDE_INO
// Correctly include the file by expanding and stringifying the macro value
#include _FASTLED_STRINGIFY(FASTLED_STUB_MAIN_INCLUDE_INO)
#else
void setup() {}
void loop() {}
#endif // FASTLED_STUB_MAIN_INCLUDE_INO
#include <iostream>
int main() {
// Super simple main function that just calls the setup and loop functions.
setup();
while(1) {
loop();
}
}
#endif // FASTLED_STUB_MAIN_INCLUDE_INO

View File

@@ -0,0 +1,10 @@
#pragma once
namespace fl {
enum SuperSample {
SUPER_SAMPLE_NONE = 1, // 1x supersampling (no supersampling)
SUPER_SAMPLE_2X = 2, // 2x supersampling
SUPER_SAMPLE_4X = 4, // 4x supersampling
SUPER_SAMPLE_8X = 8 // 8x supersampling
};
}

View File

@@ -0,0 +1,134 @@
#pragma once
/*
Provides eanble_if and is_derived for compilers before C++14.
*/
#include <stdint.h>
#include "fl/namespace.h"
namespace fl { // mandatory namespace to prevent name collision with std::enable_if.
// Define enable_if for SFINAE
template <bool Condition, typename T = void>
struct enable_if {};
// Specialization for true condition
template <typename T>
struct enable_if<true, T> {
using type = T;
};
// if enable_if<Condition, T> is true, then there will be a member type
// called type. Otherwise it will not exist. This is (ab)used to enable
// constructors and other functions based on template parameters. If there
// is no member type, then the compiler will not fail to bind to the target
// function or operation.
template <bool Condition, typename T = void>
using enable_if_t = typename enable_if<Condition, T>::type;
// Define is_base_of to check inheritance relationship
template <typename Base, typename Derived>
struct is_base_of {
private:
typedef uint8_t yes;
typedef uint16_t no;
static yes test(Base*); // Matches if Derived is convertible to Base*
static no test(...); // Fallback if not convertible
enum {
kSizeDerived = sizeof(test(static_cast<Derived*>(nullptr))),
};
public:
static constexpr bool value = (kSizeDerived == sizeof(yes));
};
// Define is_base_of_v for compatibility with pre-C++14
// Replaced variable template with a constant static member
template <typename Base, typename Derived>
struct is_base_of_v_helper {
static constexpr bool value = is_base_of<Base, Derived>::value;
};
// Define is_same trait
template <typename T, typename U>
struct is_same {
static constexpr bool value = false;
};
// Specialization for when T and U are the same type
template <typename T>
struct is_same<T, T> {
static constexpr bool value = true;
};
// Define is_same_v for compatibility with variable templates
template <typename T, typename U>
struct is_same_v_helper {
static constexpr bool value = is_same<T, U>::value;
};
// Define is_pod trait (basic implementation)
template <typename T>
struct is_pod {
static constexpr bool value = false; // Default to false for safety
};
// Specializations for fundamental types
template<> struct is_pod<bool> { static constexpr bool value = true; };
template<> struct is_pod<char> { static constexpr bool value = true; };
template<> struct is_pod<signed char> { static constexpr bool value = true; };
template<> struct is_pod<unsigned char> { static constexpr bool value = true; };
template<> struct is_pod<short> { static constexpr bool value = true; };
template<> struct is_pod<unsigned short> { static constexpr bool value = true; };
template<> struct is_pod<int> { static constexpr bool value = true; };
template<> struct is_pod<unsigned int> { static constexpr bool value = true; };
template<> struct is_pod<long> { static constexpr bool value = true; };
template<> struct is_pod<unsigned long> { static constexpr bool value = true; };
template<> struct is_pod<long long> { static constexpr bool value = true; };
template<> struct is_pod<unsigned long long> { static constexpr bool value = true; };
template<> struct is_pod<float> { static constexpr bool value = true; };
template<> struct is_pod<double> { static constexpr bool value = true; };
template<> struct is_pod<long double> { static constexpr bool value = true; };
// Helper struct for is_pod_v (similar to other _v helpers)
template <typename T>
struct is_pod_v_helper {
static constexpr bool value = is_pod<T>::value;
};
// This uses template magic to maybe generate a type for the given condition. If that type
// doesn't exist then a type will fail to be generated, and the compiler will skip the
// consideration of a target function. This is useful for enabling template constructors
// that only become available if the class can be upcasted to the desired type.
//
// Example:
// This is an optional upcasting constructor for a Ref<T>. If the type U is not derived from T
// then the constructor will not be generated, and the compiler will skip it.
//
// template <typename U, typename = fl::is_derived<T, U>>
// Ref(const Ref<U>& refptr) : referent_(refptr.get());
template <typename Base, typename Derived>
using is_derived = enable_if_t<is_base_of<Base, Derived>::value>;
} // namespace fl
// For comparison operators that return bool against pod data. The class obj will need
// to supply the comparison operator for the pod type.
// This example will show how to define a comparison operator for a class that can be
// compared against a pod type.
// Example:
// FASTLED_DEFINE_POD_COMPARISON_OPERATOR(Myclass, >=) will allow MyClass to be compared
// MyClass obj;
// return obj >= 0;
#define FASTLED_DEFINE_POD_COMPARISON_OPERATOR(CLASS, OP) \
template <typename T, typename U> \
typename fl::enable_if<fl::is_same<U, CLASS>::value && fl::is_pod<T>::value, bool>::type \
operator OP (const T& pod, const CLASS& obj) { return pod OP obj; } \
template <typename T> \
typename fl::enable_if<fl::is_pod<T>::value, bool>::type \
operator OP (const CLASS& obj, const T& pod) { return obj OP pod; }

View File

@@ -0,0 +1,108 @@
#include "fl/time_alpha.h"
#include "fl/warn.h"
#include "math_macros.h"
namespace fl {
uint8_t time_alpha8(uint32_t now, uint32_t start, uint32_t end) {
if (now < start) {
return 0;
}
if (now > end) {
return 255;
}
uint32_t elapsed = now - start;
uint32_t total = end - start;
uint32_t out = (elapsed * 255) / total;
if (out > 255) {
out = 255;
}
return static_cast<uint8_t>(out);
}
uint16_t time_alpha16(uint32_t now, uint32_t start, uint32_t end) {
if (now < start) {
return 0;
}
if (now > end) {
return 65535;
}
uint32_t elapsed = now - start;
uint32_t total = end - start;
uint32_t out = (elapsed * 65535) / total;
if (out > 65535) {
out = 65535;
}
return static_cast<uint16_t>(out);
}
TimeRamp::TimeRamp(uint32_t risingTime, uint32_t latchMs,
uint32_t fallingTime)
: mLatchMs(latchMs), mRisingTime(risingTime), mFallingTime(fallingTime) {}
void TimeRamp::trigger(uint32_t now) {
mStart = now;
// mLastValue = 0;
mFinishedRisingTime = mStart + mRisingTime;
mFinishedPlateauTime = mFinishedRisingTime + mLatchMs;
mFinishedFallingTime = mFinishedPlateauTime + mFallingTime;
}
void TimeRamp::trigger(uint32_t now, uint32_t risingTime,
uint32_t latchMs, uint32_t fallingTime) {
mRisingTime = risingTime;
mLatchMs = latchMs;
mFallingTime = fallingTime;
trigger(now);
}
bool TimeRamp::isActive(uint32_t now) const {
bool not_started = (mFinishedRisingTime == 0) && (mFinishedPlateauTime == 0) &&
(mFinishedFallingTime == 0);
if (not_started) {
// if we have not started, we are not active
return false;
}
if (now < mStart) {
// if the time is before the start, we are not active
return false;
}
if (now > mFinishedFallingTime) {
// if the time is after the finished rising, we are not active
return false;
}
return true;
}
uint8_t TimeRamp::update(uint32_t now) {
if (!isActive(now)) {
return 0;
}
// uint32_t elapsed = now - mStart;
uint8_t out = 0;
if (now < mFinishedRisingTime) {
out = time_alpha8(now, mStart, mFinishedRisingTime);
} else if (now < mFinishedPlateauTime) {
// plateau
out = 255;
} else if (now < mFinishedFallingTime) {
// ramp down
uint8_t alpha =
time_alpha8(now, mFinishedPlateauTime, mFinishedFallingTime);
out = 255 - alpha;
} else {
// finished
out = 0;
}
mLastValue = out;
return out;
}
} // namespace fl

132
FastLED/src/fl/time_alpha.h Normal file
View File

@@ -0,0 +1,132 @@
#pragma once
#include <stdint.h>
namespace fl {
// Use this function to compute the alpha value based on the time elapsed
// 0 -> 255
uint8_t time_alpha8(uint32_t now, uint32_t start, uint32_t end);
// 0 -> 65535
uint16_t time_alpha16(uint32_t now, uint32_t start, uint32_t end);
class TimeAlpha {
public:
virtual ~TimeAlpha() = default;
virtual void trigger(uint32_t now) = 0;
virtual uint8_t update(uint32_t now) = 0;
virtual uint16_t update16(uint32_t now) {
return static_cast<uint16_t>(update(now) << 8);
}
virtual bool isActive(uint32_t now) const = 0;
};
/*
* amplitude
* ^
* 255 ───────────────────────
* / \
* / \
* / \
* / \
* 0 ────────────┴ ┴──────────────────> time (ms)
* t0 t1 t2 t4
*
*
*
*/
class TimeRamp: public TimeAlpha {
public:
/// @param latchMs total active time (ms)
/// @param risingTime time to ramp from 0→255 (ms)
/// @param fallingTime time to ramp from 255→0 (ms)
TimeRamp(uint32_t risingTime, uint32_t latchMs, uint32_t fallingTime);
/// Call this when you want to (re)start the ramp cycle.
void trigger(uint32_t now) override;
void trigger(uint32_t now, uint32_t risingTime, uint32_t latchMs,
uint32_t fallingTime);
/// @return true iff we're still within the latch period.
bool isActive(uint32_t now) const override;
/// Compute current 0255 output based on how much time has elapsed since
/// trigger().
uint8_t update(uint32_t now) override;
private:
uint32_t mLatchMs;
uint32_t mRisingTime;
uint32_t mFallingTime;
uint32_t mFinishedRisingTime = 0;
uint32_t mFinishedPlateauTime = 0;
uint32_t mFinishedFallingTime = 0;
uint32_t mStart = 0;
uint8_t mLastValue = 0;
};
/*
* amplitude
* ^
* 255 ──────────────────────────────────────
* /
* /
* /
* /
* 0 ────────────┴ --> time (ms)
* t0 t1
*
*
*
*/
class TimeLinear: TimeAlpha {
public:
TimeLinear(uint32_t duration) : mDuration(duration) {}
void trigger(uint32_t now) override {
mStart = now;
mEnd = now + mDuration;
}
bool isActive(uint32_t now) const override {
bool not_started = (mEnd == 0) && (mStart == 0);
if (not_started) {
// if we have not started, we are not active
return false;
}
if (now < mStart) {
// if the time is before the start, we are not active
return false;
}
if (now > mEnd) {
// if the time is after the finished rising, we are not active
return false;
}
return true;
}
uint8_t update(uint32_t now) override {
bool not_started = (mEnd == 0) && (mStart == 0);
if (not_started) {
// if we have not started, we are not active
return 0;
}
uint8_t out = time_alpha8(now, mStart, mEnd);
return out;
}
private:
uint32_t mStart = 0;
uint32_t mDuration = 0;
uint32_t mEnd = 0;
};
} // namespace fl

15
FastLED/src/fl/types.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "led_sysdefs.h"
#include "fl/namespace.h"
namespace fl {
#if defined(__AVR__)
typedef int cycle_t; ///< 8.8 fixed point (signed) value
#else
typedef int64_t cycle_t; ///< 8.8 fixed point (signed) value
#endif
}

196
FastLED/src/fl/ui.h Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include <stdint.h>
#include "fl/math_macros.h"
#include "fl/namespace.h"
#include "fl/template_magic.h"
#include "fl/unused.h"
#include "platforms/ui_defs.h"
#ifndef FASTLED_HAS_UI_SLIDER
#define FASTLED_HAS_UI_SLIDER 0
#endif
#ifndef FASTLED_HAS_UI_BUTTON
#define FASTLED_HAS_UI_BUTTON 0
#endif
#ifndef FASTLED_HAS_UI_CHECKBOX
#define FASTLED_HAS_UI_CHECKBOX 0
#endif
#ifndef FASTLED_HAS_UI_NUMBER_FIELD
#define FASTLED_HAS_UI_NUMBER_FIELD 0
#endif
#ifndef FASTLED_HAS_UI_TITLE
#define FASTLED_HAS_UI_TITLE 0
#endif
#ifndef FASTLED_HAS_UI_DESCRIPTION
#define FASTLED_HAS_UI_DESCRIPTION 0
#endif
namespace fl {
// If the platform is missing ui components, provide stubs.
#if !FASTLED_HAS_UI_SLIDER
class UISlider {
public:
// If step is -1, it will be calculated as (max - min) / 100
UISlider(const char *name, float value = 128.0f, float min = 1,
float max = 255, float step = -1.f)
: mValue(value), mMin(MIN(min, max)), mMax(MAX(min, max)) {
FASTLED_UNUSED(name);
FASTLED_UNUSED(step);
if (value < min) {
mValue = min;
}
if (value > max) {
mValue = max;
}
}
~UISlider() {}
float value() const { return mValue; }
float value_normalized() const {
if (ALMOST_EQUAL(mMax, mMin, 0.0001f)) {
return 0;
}
return (mValue - mMin) / (mMax - mMin);
}
float max_value() const { return mMax; }
void setValue(float value) { mValue = MAX(mMin, MIN(mMax, value)); }
operator float() const { return mValue; }
operator uint8_t() const { return static_cast<uint8_t>(mValue); }
operator uint16_t() const { return static_cast<uint16_t>(mValue); }
operator int() const { return static_cast<int>(mValue); }
template <typename T> T as() const { return static_cast<T>(mValue); }
UISlider &operator=(float value) {
setValue(value);
return *this;
}
UISlider &operator=(int value) {
setValue(static_cast<float>(value));
return *this;
}
private:
float mValue;
float mMin;
float mMax;
};
// template operator for >= against a jsSlider
#endif
#if !FASTLED_HAS_UI_BUTTON
class UIButton {
public:
UIButton(const char *name) { FASTLED_UNUSED(name); }
~UIButton() {}
bool isPressed() const { return false; }
bool clicked() const { return false; }
int clickedCount() const { return 0; }
operator bool() const { return false; }
};
#endif
#if !FASTLED_HAS_UI_CHECKBOX
class UICheckbox {
public:
UICheckbox(const char *name, bool value = false) : mValue(value) {
FASTLED_UNUSED(name);
}
~UICheckbox() {}
operator bool() const { return mValue; }
operator int() const { return mValue ? 1 : 0; }
UICheckbox &operator=(bool value) {
setValue(value);
return *this;
}
UICheckbox &operator=(int value) {
setValue(value != 0);
return *this;
}
private:
void setValue(bool value) { mValue = value; }
bool mValue;
};
#endif
#if !FASTLED_HAS_UI_NUMBER_FIELD
class UINumberField {
public:
UINumberField(const char *name, double value, double min = 0,
double max = 100)
: mValue(value), mMin(MIN(min, max)), mMax(MAX(min, max)) {
FASTLED_UNUSED(name);
}
~UINumberField() {}
double value() const { return mValue; }
void setValue(double value) { mValue = MAX(mMin, MIN(mMax, value)); }
operator double() const { return mValue; }
operator int() const { return static_cast<int>(mValue); }
UINumberField &operator=(double value) {
setValue(value);
return *this;
}
UINumberField &operator=(int value) {
setValue(static_cast<double>(value));
return *this;
}
private:
double mValue;
double mMin;
double mMax;
};
#endif
#if !FASTLED_HAS_UI_TITLE
class UITitle {
public:
UITitle(const char *name) { FASTLED_UNUSED(name); }
~UITitle() {}
};
#endif
#if !FASTLED_HAS_UI_DESCRIPTION
class UIDescription {
public:
UIDescription(const char *name) { FASTLED_UNUSED(name); }
~UIDescription() {}
};
#endif
#define FASTLED_UI_DEFINE_OPERATORS(UI_CLASS) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, >=) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, <=) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, >) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, <) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, ==) \
FASTLED_DEFINE_POD_COMPARISON_OPERATOR(UI_CLASS, !=)
FASTLED_UI_DEFINE_OPERATORS(UISlider);
FASTLED_UI_DEFINE_OPERATORS(UINumberField);
FASTLED_UI_DEFINE_OPERATORS(UICheckbox);
FASTLED_UI_DEFINE_OPERATORS(UIButton);
} // end namespace fl

3
FastLED/src/fl/unused.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#define FASTLED_UNUSED(x) (void)(x)

659
FastLED/src/fl/vector.h Normal file
View File

@@ -0,0 +1,659 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "inplacenew.h"
#include "fl/namespace.h"
#include "fl/scoped_ptr.h"
#include "fl/insert_result.h"
namespace fl {
// A fixed sized vector. The user is responsible for making sure that the
// inserts do not exceed the capacity of the vector, otherwise they will fail.
// Because of this limitation, this vector is not a drop in replacement for
// std::vector.
template<typename T, size_t N>
class FixedVector {
private:
union {
char mRaw[N * sizeof(T)];
T mData[N];
};
size_t current_size = 0;
public:
typedef T* iterator;
typedef const T* const_iterator;
// Constructor
constexpr FixedVector() : current_size(0) {}
FixedVector(const T (&values)[N]) : current_size(N) {
assign(values, N);
}
template<size_t M>
FixedVector(const T (&values)[M]) : current_size(M) {
static_assert(M <= N, "Too many elements for FixedVector");
assign(values, M);
}
// Destructor
~FixedVector() {
clear();
}
// Array subscript operator
T& operator[](size_t index) {
return mData[index];
}
// Const array subscript operator
const T& operator[](size_t index) const {
if (index >= current_size) {
const T* out = nullptr;
return *out; // Cause a nullptr dereference
}
return mData[index];
}
// Get the current size of the vector
constexpr size_t size() const {
return current_size;
}
constexpr bool empty() const {
return current_size == 0;
}
// Get the capacity of the vector
constexpr size_t capacity() const {
return N;
}
// Add an element to the end of the vector
void push_back(const T& value) {
if (current_size < N) {
void* mem = &mData[current_size];
new (mem) T(value);
++current_size;
}
}
void assign(const T* values, size_t count) {
clear();
for (size_t i = 0; i < count; ++i) {
push_back(values[i]);
}
}
void assign(const_iterator begin, const_iterator end) {
clear();
for (const_iterator it = begin; it != end; ++it) {
push_back(*it);
}
}
// Remove the last element from the vector
void pop_back() {
if (current_size > 0) {
--current_size;
mData[current_size].~T();
}
}
// Clear the vector
void clear() {
while (current_size > 0) {
pop_back();
}
}
// Erase the element at the given iterator position
iterator erase(iterator pos) {
if (pos != end()) {
pos->~T();
// shift all elements to the left
for (iterator p = pos; p != end() - 1; ++p) {
new (p) T(*(p + 1)); // Use copy constructor instead of std::move
(p + 1)->~T();
}
--current_size;
}
return pos;
}
iterator erase(const T& value) {
iterator it = find(value);
if (it != end()) {
erase(it);
}
return it;
}
iterator find(const T& value) {
for (iterator it = begin(); it != end(); ++it) {
if (*it == value) {
return it;
}
}
return end();
}
template<typename Predicate>
iterator find_if(Predicate pred) {
for (iterator it = begin(); it != end(); ++it) {
if (pred(*it)) {
return it;
}
}
return end();
}
bool insert(iterator pos, const T& value) {
if (current_size < N) {
// shift all elements to the right
for (iterator p = end(); p != pos; --p) {
new (p) T(*(p - 1)); // Use copy constructor instead of std::move
(p - 1)->~T();
}
new (pos) T(value);
++current_size;
return true;
}
return false;
}
const_iterator find(const T& value) const {
for (const_iterator it = begin(); it != end(); ++it) {
if (*it == value) {
return it;
}
}
return end();
}
iterator data() {
return begin();
}
const_iterator data() const {
return begin();
}
bool has(const T& value) const {
return find(value) != end();
}
// Access to first and last elements
T& front() {
return mData[0];
}
const T& front() const {
return mData[0];
}
T& back() {
return mData[current_size - 1];
}
const T& back() const {
return mData[current_size - 1];
}
// Iterator support
iterator begin() { return &mData[0]; }
const_iterator begin() const { return &mData[0]; }
iterator end() { return &mData[current_size]; }
const_iterator end() const { return &mData[current_size]; }
};
template<typename T>
class HeapVector {
private:
fl::scoped_array<T> mArray;
size_t mCapacity = 0;
size_t mSize = 0;
public:
typedef T* iterator;
typedef const T* const_iterator;
struct reverse_iterator {
iterator it;
reverse_iterator(iterator i): it(i) {}
T& operator*() { return *(it - 1); }
reverse_iterator& operator++() { --it; return *this; }
bool operator!=(const reverse_iterator& other) const { return it != other.it; }
};
// Constructor
HeapVector(size_t size = 0, const T& value = T()): mCapacity(size) {
mArray.reset(new T[mCapacity]);
for (size_t i = 0; i < size; ++i) {
mArray[i] = value;
}
mSize = size;
}
HeapVector(const HeapVector<T>& other): mSize(other.size()) {
assign(other.begin(), other.end());
}
HeapVector& operator=(const HeapVector<T>& other) { // cppcheck-suppress operatorEqVarError
if (this != &other) {
assign(other.begin(), other.end());
}
return *this;
}
// Destructor
~HeapVector() {
clear();
}
void ensure_size(size_t n) {
if (n > mCapacity) {
size_t new_capacity = (3*mCapacity) / 2;
if (new_capacity < n) {
new_capacity = n;
}
fl::scoped_array<T> new_array(new T[new_capacity]);
for (size_t i = 0; i < mSize; ++i) {
new_array[i] = mArray[i];
}
// mArray = std::move(new_array);
mArray.reset();
mArray.reset(new_array.release());
mCapacity = new_capacity;
}
}
void reserve(size_t n) {
if (n > mCapacity) {
ensure_size(n);
}
}
void resize(size_t n) {
if (mSize == n) {
return;
}
HeapVector<T> temp(n);
for (size_t i = 0; i < n && i < mSize; ++i) {
temp.mArray[i] = mArray[i];
}
swap(temp);
}
void resize(size_t n, const T& value) {
mArray.reset();
mArray.reset(new T[n]);
for (size_t i = 0; i < n; ++i) {
mArray[i] = value;
}
mCapacity = n;
}
// Array access operators
T& operator[](size_t index) {
return mArray[index];
}
const T& operator[](size_t index) const {
return mArray[index];
}
// Capacity and size methods
size_t size() const {
return mSize;
}
bool empty() const {
return mSize == 0;
}
size_t capacity() const {
return mCapacity;
}
// Element addition/removal
void push_back(const T& value) {
ensure_size(mSize + 1);
if (mSize < mCapacity) {
mArray[mSize] = value;
++mSize;
}
}
void pop_back() {
if (mSize > 0) {
--mSize;
mArray[mSize] = T();
}
}
void clear() {
while (mSize > 0) {
pop_back();
}
}
// Iterator methods
iterator begin() { return &mArray[0]; }
const_iterator begin() const { return &mArray[0]; }
iterator end() { return &mArray[mSize]; }
const_iterator end() const { return &mArray[mSize]; }
reverse_iterator rbegin() {
return reverse_iterator(end());
}
reverse_iterator rend() {
return reverse_iterator(begin());
}
// Element access
T& front() {
return mArray[0];
}
const T& front() const {
return mArray[0];
}
T& back() {
return mArray[mSize - 1];
}
const T& back() const {
return mArray[mSize - 1];
}
// Search and modification
iterator find(const T& value) {
for (iterator it = begin(); it != end(); ++it) {
if (*it == value) {
return it;
}
}
return end();
}
const_iterator find(const T& value) const {
for (const_iterator it = begin(); it != end(); ++it) {
if (*it == value) {
return it;
}
}
return end();
}
template<typename Predicate>
iterator find_if(Predicate pred) {
for (iterator it = begin(); it != end(); ++it) {
if (pred(*it)) {
return it;
}
}
return end();
}
bool has(const T& value) const {
return find(value) != end();
}
bool erase(iterator pos, T* out_value = nullptr) {
if (pos == end() || empty()) {
return false;
}
if (out_value) {
*out_value = *pos;
}
while (pos != end() - 1) {
*pos = *(pos + 1);
++pos;
}
back() = T();
--mSize;
return true;
}
void erase(const T& value) {
iterator it = find(value);
if (it != end()) {
erase(it);
}
}
void swap(HeapVector<T>& other) {
T* temp = mArray.release();
size_t temp_size = mSize;
size_t temp_capacity = mCapacity;
T* temp2 = other.mArray.release();
mArray.reset(temp2);
other.mArray.reset(temp);
mSize = other.mSize;
mCapacity = other.mCapacity;
other.mSize = temp_size;
other.mCapacity = temp_capacity;
}
void swap(iterator a, iterator b) {
T temp = *a;
*a = *b;
*b = temp;
}
bool full() const {
return mSize >= mCapacity;
}
bool insert(iterator pos, const T& value) {
// TODO: Introduce mMaxSize (and move it from SortedVector to here)
// push back and swap into place.
size_t target_idx = pos - begin();
push_back(value);
auto last = end() - 1;
for (size_t curr_idx = last - begin(); curr_idx > target_idx; --curr_idx) {
auto first = begin() + curr_idx - 1;
auto second = begin() + curr_idx;
swap(first, second);
}
return true;
}
void assign(const T* values, size_t count) {
assign(values, values + count);
}
void assign(const_iterator begin, const_iterator end) {
clear();
reserve(end - begin);
for (const_iterator it = begin; it != end; ++it) {
push_back(*it);
}
}
T* data() {
return mArray.get();
}
const T* data() const {
return mArray.get();
}
bool operator==(const HeapVector<T>& other) const {
if (size() != other.size()) {
return false;
}
for (size_t i = 0; i < size(); ++i) {
if (mArray[i] != other.mArray[i]) {
return false;
}
}
return true;
}
bool operator!=(const HeapVector<T>& other) const {
return !(*this == other);
}
};
template <typename T, typename LessThan>
class SortedHeapVector {
private:
HeapVector<T> mArray;
LessThan mLess;
size_t mMaxSize = size_t(-1);
public:
typedef typename HeapVector<T>::iterator iterator;
typedef typename HeapVector<T>::const_iterator const_iterator;
SortedHeapVector(LessThan less=LessThan()): mLess(less) {}
void setMaxSize(size_t n) {
if (mMaxSize == n) {
return;
}
mMaxSize = n;
const bool needs_adjustment = mArray.size() > mMaxSize;
if (needs_adjustment) {
mArray.resize(n);
} else {
mArray.reserve(n);
}
}
~SortedHeapVector() {
mArray.clear();
}
void reserve(size_t n) {
mArray.reserve(n);
}
// Insert while maintaining sort order
bool insert(const T& value, InsertResult* result = nullptr) {
// Find insertion point using binary search
iterator pos = lower_bound(value);
if (pos != end() && !mLess(value, *pos) && !mLess(*pos, value)) {
// return false; // Already inserted.
if (result) {
// *result = kExists;
*result = InsertResult::kExists;
}
return false;
}
if (mArray.size() >= mMaxSize) {
// return false; // Too full
if (result) {
*result = InsertResult::kMaxSize;
}
return false;
}
mArray.insert(pos, value);
if (result) {
*result = kInserted;
}
return true;
}
// Find the first position where we should insert value to maintain sort order
iterator lower_bound(const T& value) {
iterator first = mArray.begin();
iterator last = mArray.end();
while (first != last) {
iterator mid = first + (last - first) / 2;
if (mLess(*mid, value)) {
first = mid + 1;
} else {
last = mid;
}
}
return first;
}
const_iterator lower_bound(const T& value) const {
return const_cast<SortedHeapVector*>(this)->lower_bound(value);
}
// Lookup operations
iterator find(const T& value) {
iterator pos = lower_bound(value);
if (pos != end() && !mLess(value, *pos) && !mLess(*pos, value)) {
return pos;
}
return end();
}
void swap(SortedHeapVector& other) {
mArray.swap(other.mArray);
}
const_iterator find(const T& value) const {
return const_cast<SortedHeapVector*>(this)->find(value);
}
bool has(const T& value) const {
return find(value) != end();
}
// Removal operations
bool erase(const T& value) {
iterator it = find(value);
if (it != end()) {
return mArray.erase(it);
}
return false;
}
bool erase(iterator pos) {
return mArray.erase(pos);
}
// Basic container operations
size_t size() const { return mArray.size(); }
bool empty() const { return mArray.empty(); }
size_t capacity() const { return mArray.capacity(); }
void clear() { mArray.clear(); }
bool full() const {
if (mArray.size() >= mMaxSize) {
return true;
}
return mArray.full();
}
// Element access
T& operator[](size_t index) { return mArray[index]; }
const T& operator[](size_t index) const { return mArray[index]; }
T& front() { return mArray.front(); }
const T& front() const { return mArray.front(); }
T& back() { return mArray.back(); }
const T& back() const { return mArray.back(); }
// Iterators
iterator begin() { return mArray.begin(); }
const_iterator begin() const { return mArray.begin(); }
iterator end() { return mArray.end(); }
const_iterator end() const { return mArray.end(); }
// Raw data access
T* data() { return mArray.data(); }
const T* data() const { return mArray.data(); }
};
} // namespace fl

View File

@@ -0,0 +1,7 @@
#pragma once
#ifdef __AVR__
#define VIRTUAL_IF_NOT_AVR
#else
#define VIRTUAL_IF_NOT_AVR virtual
#endif

9
FastLED/src/fl/warn.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include "fl/dbg.h"
#ifndef FASTLED_WARN
// in the future this will do something
#define FASTLED_WARN FASTLED_DBG
#define FASTLED_WARN_IF FASTLED_DBG_IF
#endif

View File

@@ -0,0 +1,289 @@
// Based on works and code by Shawn Silverman.
#include <stdint.h>
#include "fl/namespace.h"
#include "fl/wave_simulation.h"
namespace {
uint8_t half_duplex_blend_sqrt_q15(uint16_t x) {
x = MIN(x, 32767); // Q15
const int Q = 15;
uint32_t X = (uint32_t)x << Q; // promote to Q30
uint32_t y = (1u << Q); // start at “1.0” in Q15
// 34 iterations is plenty for 15bit precision:
for (int i = 0; i < 4; i++) {
y = ( y + (X / y) ) >> 1;
}
return static_cast<int16_t>(y) >> 8;
}
uint8_t half_duplex_blend_linear(uint16_t x) {
x = MIN(x, 32767); // Q15
x *= 2;
return x >> 8;
}
} // namespace
namespace fl {
void WaveSimulation2D::setSpeed(float speed) { sim->setSpeed(speed); }
WaveSimulation2D::WaveSimulation2D(uint32_t W, uint32_t H, SuperSample factor,
float speed, float dampening) {
init(W, H, factor, speed, dampening);
}
void WaveSimulation2D::init(uint32_t width, uint32_t height, SuperSample factor, float speed, int dampening) {
outerWidth = width;
outerHeight = height;
multiplier = static_cast<uint32_t>(factor);
sim.reset(new WaveSimulation2D_Real(width * multiplier, height * multiplier, speed, dampening));
// Extra frames are needed because the simulation slows down in
// proportion to the supersampling factor.
extraFrames = uint8_t(factor) - 1;
}
void WaveSimulation2D::setDampening(int damp) { sim->setDampening(damp); }
int WaveSimulation2D::getDampenening() const { return sim->getDampenening(); }
float WaveSimulation2D::getSpeed() const { return sim->getSpeed(); }
float WaveSimulation2D::getf(size_t x, size_t y) const {
if (!has(x, y))
return 0.0f;
float sum = 0.0f;
for (uint32_t j = 0; j < multiplier; ++j) {
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->getf(x * multiplier + i, y * multiplier + j);
}
}
return sum / static_cast<float>(multiplier * multiplier);
}
int16_t WaveSimulation2D::geti16(size_t x, size_t y) const {
if (!has(x, y))
return 0;
int32_t sum = 0;
for (uint32_t j = 0; j < multiplier; ++j) {
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->geti16(x * multiplier + i, y * multiplier + j);
}
}
return static_cast<int16_t>(sum / (multiplier * multiplier));
}
int16_t WaveSimulation2D::geti16Previous(size_t x, size_t y) const {
if (!has(x, y))
return 0;
int32_t sum = 0;
for (uint32_t j = 0; j < multiplier; ++j) {
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->geti16Previous(x * multiplier + i, y * multiplier + j);
}
}
return static_cast<int16_t>(sum / (multiplier * multiplier));
}
bool WaveSimulation2D::geti16All(size_t x, size_t y, int16_t *curr,
int16_t *prev, int16_t *diff) const {
if (!has(x, y))
return false;
*curr = geti16(x, y);
*prev = geti16Previous(x, y);
*diff = *curr - *prev;
return true;
}
int8_t WaveSimulation2D::geti8(size_t x, size_t y) const {
return static_cast<int8_t>(geti16(x, y) >> 8);
}
uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
int16_t value = geti16(x, y);
if (sim->getHalfDuplex()) {
uint16_t v2 = static_cast<uint16_t>(value);
switch (mU8Mode) {
case WAVE_U8_MODE_LINEAR:
return half_duplex_blend_linear(v2);
case WAVE_U8_MODE_SQRT:
return half_duplex_blend_sqrt_q15(v2);
}
}
return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
}
bool WaveSimulation2D::has(size_t x, size_t y) const {
return (x < outerWidth) && (y < outerHeight);
}
void WaveSimulation2D::seti16(size_t x, size_t y, int16_t v16) {
if (!has(x, y))
return;
// radius in pixels of your diamond
int rad = static_cast<int>(multiplier) / 2;
for (size_t j = 0; j < multiplier; ++j) {
for (size_t i = 0; i < multiplier; ++i) {
// compute offset from the center of this block
int dx = static_cast<int>(i) - rad;
int dy = static_cast<int>(j) - rad;
// keep only those points whose Manhattan distance ≤ rad
if (ABS(dx) + ABS(dy) > rad)
continue;
size_t xx = x * multiplier + i;
size_t yy = y * multiplier + j;
if (sim->has(xx, yy)) {
sim->seti16(xx, yy, v16);
}
}
}
}
void WaveSimulation2D::setf(size_t x, size_t y, float value) {
if (!has(x, y))
return;
int16_t v16 = wave_detail::float_to_fixed(value);
seti16(x, y, v16);
}
void WaveSimulation2D::update() {
sim->update();
for (uint8_t i = 0; i < extraFrames; ++i) {
sim->update();
}
}
uint32_t WaveSimulation2D::getWidth() const { return outerWidth; }
uint32_t WaveSimulation2D::getHeight() const { return outerHeight; }
void WaveSimulation2D::setExtraFrames(uint8_t extra) { extraFrames = extra; }
WaveSimulation1D::WaveSimulation1D(uint32_t length, SuperSample factor,
float speed, int dampening) {
init(length, factor, speed, dampening);
}
void WaveSimulation1D::init(uint32_t length, SuperSample factor,
float speed, int dampening) {
outerLength = length;
multiplier = static_cast<uint32_t>(factor);
sim.reset(new WaveSimulation1D_Real(length * multiplier, speed, dampening));
// Extra updates (frames) are applied because the simulation slows down in
// proportion to the supersampling factor.
extraFrames = static_cast<uint8_t>(factor) - 1;
}
void WaveSimulation1D::setSpeed(float speed) { sim->setSpeed(speed); }
void WaveSimulation1D::setDampening(int damp) { sim->setDampening(damp); }
int WaveSimulation1D::getDampenening() const { return sim->getDampenening(); }
void WaveSimulation1D::setExtraFrames(uint8_t extra) { extraFrames = extra; }
float WaveSimulation1D::getSpeed() const { return sim->getSpeed(); }
float WaveSimulation1D::getf(size_t x) const {
if (!has(x))
return 0.0f;
float sum = 0.0f;
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->getf(x * multiplier + i);
}
return sum / static_cast<float>(multiplier);
}
int16_t WaveSimulation1D::geti16(size_t x) const {
if (!has(x))
return 0;
int32_t sum = 0;
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->geti16(x * multiplier + i);
}
return static_cast<int16_t>(sum / multiplier);
}
int16_t WaveSimulation1D::geti16Previous(size_t x) const {
if (!has(x))
return 0;
int32_t sum = 0;
for (uint32_t i = 0; i < multiplier; ++i) {
sum += sim->geti16Previous(x * multiplier + i);
}
return static_cast<int16_t>(sum / multiplier);
}
bool WaveSimulation1D::geti16All(size_t x, int16_t *curr, int16_t *prev,
int16_t *diff) const {
if (!has(x))
return false;
*curr = geti16(x);
*prev = geti16Previous(x);
*diff = *curr - *prev;
return true;
}
int8_t WaveSimulation1D::geti8(size_t x) const {
return static_cast<int8_t>(geti16(x) >> 8);
}
// uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
// int16_t value = geti16(x, y);
// if (sim->getHalfDuplex()) {
// uint16_t v2 = static_cast<uint16_t>(value);
// switch (mU8Mode) {
// case WAVE_U8_MODE_LINEAR:
// return half_duplex_blend_linear(v2);
// case WAVE_U8_MODE_SQRT:
// return half_duplex_blend_sqrt_q15(v2);
// }
// }
// return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
// }
uint8_t WaveSimulation1D::getu8(size_t x) const {
int16_t value = geti16(x);
if (sim->getHalfDuplex()) {
uint16_t v2 = static_cast<uint16_t>(value);
switch (mU8Mode) {
case WAVE_U8_MODE_LINEAR:
return half_duplex_blend_linear(v2);
case WAVE_U8_MODE_SQRT:
return half_duplex_blend_sqrt_q15(v2);
}
}
return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
}
bool WaveSimulation1D::has(size_t x) const { return (x < outerLength); }
void WaveSimulation1D::setf(size_t x, float value) {
if (!has(x))
return;
for (uint32_t i = 0; i < multiplier; ++i) {
sim->set(x * multiplier + i, value);
}
}
void WaveSimulation1D::update() {
sim->update();
for (uint8_t i = 0; i < extraFrames; ++i) {
sim->update();
}
}
uint32_t WaveSimulation1D::getLength() const { return outerLength; }
} // namespace fl

View File

@@ -0,0 +1,217 @@
/*
This is the WaveSimulation API. These classes allow flexible super sampling
to achieve much better visual quality. They will also run the simulator
update multiple times in order to achieve consistent speed between super
sampling factors.
A super sampling value of 2x will give the best results for the CPU consumption
as most artifacts will be averaged out at this resolution.
Based on works and code by Shawn Silverman.
*/
#pragma once
#include <stdint.h>
#include "fl/math_macros.h" // if needed for MAX/MIN macros
#include "fl/namespace.h"
#include "fl/scoped_ptr.h"
#include "fl/warn.h"
#include "fl/wave_simulation_real.h"
#include "fl/ptr.h"
#include "fl/supersample.h"
#include "fl/xymap.h"
#include "fx/fx.h"
#include "fx/fx2d.h"
namespace fl {
enum U8EasingFunction {
WAVE_U8_MODE_LINEAR,
WAVE_U8_MODE_SQRT
};
// -----------------------------------------------------------------------------
// New supersampled 1D simulation class.
//
// This class mimics the supersampling logic of WaveSimulation2D.
// The constructor accepts the desired downsampled length and a supersampling
// multiplier (via a SuperSample enum). Internally, it creates a high-resolution
// simulation of size (multiplier * length), and its accessor methods average
// over or replicate across the corresponding block of high-res cells.
class WaveSimulation1D {
public:
// Constructor:
// - length: desired downsampled grid length.
// - factor: supersampling multiplier (e.g., 1x, 2x, 4x, or 8x).
// Higher values yield better quality but are more cpu intensive.
// - speed and dampening are passed on to the internal simulation.
WaveSimulation1D(uint32_t length,
SuperSample factor = SuperSample::SUPER_SAMPLE_NONE,
float speed = 0.16f, int dampening = 6);
void init(uint32_t length, SuperSample factor, float speed, int dampening);
void setSuperSample(SuperSample factor) {
if (uint32_t(factor) == multiplier) {
return;
}
init(outerLength, factor, sim->getSpeed(), sim->getDampenening());
}
// Only applies to getu8().
void setEasingMode(U8EasingFunction mode) { mU8Mode = mode; }
~WaveSimulation1D() = default;
// Delegate methods to the internal simulation.
void setSpeed(float speed);
void setDampening(int damp);
int getDampenening() const;
float getSpeed() const;
// Runs the simulator faster by updating it multiple times.
void setExtraFrames(uint8_t extra);
// Downsampled getter for the floating point value at index x.
// It averages over the corresponding 'multiplier'-sized block in the
// high-res simulation.
float getf(size_t x) const;
// Downsampled getter for the Q15 (fixed point) value at index x.
// It averages the multiplier cells of Q15 values.
int16_t geti16(size_t x) const;
int16_t geti16Previous(size_t x) const;
bool geti16All(size_t x, int16_t *curr, int16_t *prev, int16_t *diff) const;
// Downsampled getters for the 8-bit representations.
int8_t geti8(size_t x) const;
uint8_t getu8(size_t x) const;
// Check if x is within the bounds of the outer (downsampled) simulation.
bool has(size_t x) const;
// Upsampling setter: set the value at an outer grid cell x by replicating
// it to the corresponding multiplier cells in the high-res simulation.
void setf(size_t x, float value);
void setHalfDuplex(bool on) {
sim->setHalfDuplex(on);
}
// Advance the simulation one time step.
void update();
// Get the outer (downsampled) grid length.
uint32_t getLength() const;
WaveSimulation1D_Real &real() { return *sim; }
private:
uint32_t outerLength; // Length of the downsampled simulation.
uint8_t extraFrames = 0;
uint32_t multiplier; // Supersampling multiplier (e.g., 2, 4, or 8).
U8EasingFunction mU8Mode = WAVE_U8_MODE_LINEAR;
// Internal high-resolution simulation.
fl::scoped_ptr<WaveSimulation1D_Real> sim;
};
class WaveSimulation2D {
public:
// Constructor:
// - W and H specify the desired inner grid size of the downsampled
// simulation.
// - 'factor' selects the supersampling multiplier (e.g., 2x, 4x, or 8x).
// Higher values yield better quality but are more cpu intensive.
// - Internally, the simulation is created with dimensions (factor*W x
// factor*H).
// - 'speed' and 'dampening' parameters are passed on to the internal
// simulation.
WaveSimulation2D(uint32_t W, uint32_t H,
SuperSample factor = SuperSample::SUPER_SAMPLE_NONE,
float speed = 0.16f, float dampening = 6.0f);
void init(uint32_t width, uint32_t height, SuperSample factor, float speed, int dampening);
~WaveSimulation2D() = default;
// Delegated simulation methods.
void setSpeed(float speed);
void setExtraFrames(uint8_t extra);
void setDampening(int damp);
void setEasingMode(U8EasingFunction mode) { mU8Mode = mode; }
int getDampenening() const;
float getSpeed() const;
void setSuperSample(SuperSample factor) {
if (uint32_t(factor) == multiplier) {
return;
}
init(outerWidth, outerHeight, factor, sim->getSpeed(),
sim->getDampenening());
}
// Downsampled getter for the floating point value at (x,y) in the outer
// grid. It averages over the corresponding multiplier×multiplier block in
// the high-res simulation.
float getf(size_t x, size_t y) const;
// Downsampled getter for the Q15 (fixed point) value at (x,y).
// It averages the multiplier×multiplier block of Q15 values.
int16_t geti16(size_t x, size_t y) const;
int16_t geti16Previous(size_t x, size_t y) const;
bool geti16All(size_t x, size_t y, int16_t *curr, int16_t *prev,
int16_t *diff) const;
// Downsampled getters for the 8-bit representations.
int8_t geti8(size_t x, size_t y) const;
// Special function to get the value as a uint8_t for drawing / gradients.
// Ease out functions are applied to this when in half duplex mode.
uint8_t getu8(size_t x, size_t y) const;
// Check if (x,y) is within the bounds of the outer (downsampled) grid.
bool has(size_t x, size_t y) const;
// Upsampling setter: set the value at an outer grid cell (x,y) by
// replicating it to all cells of the corresponding multiplier×multiplier
// block in the high-res simulation.
void setf(size_t x, size_t y, float value);
void seti16(size_t x, size_t y, int16_t value);
void setHalfDuplex(bool on) {
sim->setHalfDuplex(on);
}
// Advance the simulation one time step.
void update();
// Get the outer grid dimensions.
uint32_t getWidth() const;
uint32_t getHeight() const;
WaveSimulation2D_Real &real() { return *sim; }
private:
uint32_t outerWidth; // Width of the downsampled (outer) grid.
uint32_t outerHeight; // Height of the downsampled (outer) grid.
uint8_t extraFrames = 0;
uint32_t multiplier; // Supersampling multiplier (e.g., 2, 4, or 8).
U8EasingFunction mU8Mode = WAVE_U8_MODE_LINEAR;
// Internal high-resolution simulation.
fl::scoped_ptr<WaveSimulation2D_Real> sim;
};
} // namespace fl

View File

@@ -0,0 +1,272 @@
// Based on works and code by Shawn Silverman.
#include <stdint.h>
#include "fl/namespace.h"
#include "fl/wave_simulation_real.h"
namespace fl {
// Define Q15 conversion constants.
#define FIXED_SCALE (1 << 15) // 32768: 1.0 in Q15
#define FIXED_ONE (FIXED_SCALE)
namespace wave_detail { // Anonymous namespace for internal linkage
// Convert float to fixed Q15.
int16_t float_to_fixed(float f) { return (int16_t)(f * FIXED_SCALE); }
// Convert fixed Q15 to float.
float fixed_to_float(int16_t f) { return ((float)f) / FIXED_SCALE; }
// // Multiply two Q15 fixed point numbers.
// int16_t fixed_mul(int16_t a, int16_t b) {
// return (int16_t)(((int32_t)a * b) >> 15);
// }
} // namespace
using namespace wave_detail;
WaveSimulation1D_Real::WaveSimulation1D_Real(uint32_t len, float courantSq,
int dampening)
: length(len),
grid1(new int16_t[length + 2]()), // Allocate and zero-initialize with
// length+2 elements.
grid2(new int16_t[length + 2]()), whichGrid(0),
mCourantSq(float_to_fixed(courantSq)), mDampenening(dampening) {
// Additional initialization can be added here if needed.
}
void WaveSimulation1D_Real::setSpeed(float something) {
mCourantSq = float_to_fixed(something);
}
void WaveSimulation1D_Real::setDampening(int damp) { mDampenening = damp; }
int WaveSimulation1D_Real::getDampenening() const { return mDampenening; }
float WaveSimulation1D_Real::getSpeed() const {
return fixed_to_float(mCourantSq);
}
int16_t WaveSimulation1D_Real::geti16(size_t x) const {
if (x >= length) {
FASTLED_WARN("Out of range.");
return 0;
}
const int16_t *curr = (whichGrid == 0) ? grid1.get() : grid2.get();
return curr[x + 1];
}
int16_t WaveSimulation1D_Real::geti16Previous(size_t x) const {
if (x >= length) {
FASTLED_WARN("Out of range.");
return 0;
}
const int16_t *prev = (whichGrid == 0) ? grid2.get() : grid1.get();
return prev[x + 1];
}
float WaveSimulation1D_Real::getf(size_t x) const {
if (x >= length) {
FASTLED_WARN("Out of range.");
return 0.0f;
}
// Retrieve value from the active grid (offset by 1 for boundary).
const int16_t *curr = (whichGrid == 0) ? grid1.get() : grid2.get();
return fixed_to_float(curr[x + 1]);
}
bool WaveSimulation1D_Real::has(size_t x) const { return (x < length); }
void WaveSimulation1D_Real::set(size_t x, float value) {
if (x >= length) {
FASTLED_WARN("warning X value too high");
return;
}
int16_t *curr = (whichGrid == 0) ? grid1.get() : grid2.get();
curr[x + 1] = float_to_fixed(value);
}
void WaveSimulation1D_Real::update() {
int16_t *curr = (whichGrid == 0) ? grid1.get() : grid2.get();
int16_t *next = (whichGrid == 0) ? grid2.get() : grid1.get();
// Update boundaries with a Neumann (zero-gradient) condition:
curr[0] = curr[1];
curr[length + 1] = curr[length];
// Compute dampening factor as an integer value: 2^(mDampenening)
int32_t dampening_factor = 1 << mDampenening;
int32_t mCourantSq32 = static_cast<int32_t>(mCourantSq);
// Iterate over each inner cell.
for (size_t i = 1; i < length + 1; i++) {
// Compute the 1D Laplacian:
// lap = curr[i+1] - 2 * curr[i] + curr[i-1]
int32_t lap =
(int32_t)curr[i + 1] - ((int32_t)curr[i] << 1) + curr[i - 1];
// Multiply the Laplacian by the simulation speed using Q15 arithmetic:
int32_t term = (mCourantSq32 * lap) >> 15;
// Compute the new value:
// f = -next[i] + 2 * curr[i] + term
int32_t f = -(int32_t)next[i] + ((int32_t)curr[i] << 1) + term;
// Apply damping:
f = f - (f / dampening_factor);
// Clamp the result to the Q15 range [-32768, 32767].
if (f > 32767)
f = 32767;
else if (f < -32768)
f = -32768;
next[i] = (int16_t)f;
}
if (mHalfDuplex) {
// Set the negative values to zero.
for (size_t i = 1; i < length + 1; i++) {
if (next[i] < 0) {
next[i] = 0;
}
}
}
// Toggle the active grid.
whichGrid ^= 1;
}
WaveSimulation2D_Real::WaveSimulation2D_Real(uint32_t W, uint32_t H,
float speed, float dampening)
: width(W), height(H), stride(W + 2),
grid1(new int16_t[(W + 2) * (H + 2)]()),
grid2(new int16_t[(W + 2) * (H + 2)]()), whichGrid(0),
// Initialize speed 0.16 in fixed Q15
mCourantSq(float_to_fixed(speed)),
// Dampening exponent; e.g., 6 means a factor of 2^6 = 64.
mDampening(dampening) {}
void WaveSimulation2D_Real::setSpeed(float something) {
mCourantSq = float_to_fixed(something);
}
void WaveSimulation2D_Real::setDampening(int damp) { mDampening = damp; }
int WaveSimulation2D_Real::getDampenening() const { return mDampening; }
float WaveSimulation2D_Real::getSpeed() const {
return fixed_to_float(mCourantSq);
}
float WaveSimulation2D_Real::getf(size_t x, size_t y) const {
if (x >= width || y >= height) {
FASTLED_WARN("Out of range: " << x << ", " << y);
return 0.0f;
}
const int16_t *curr = (whichGrid == 0 ? grid1.get() : grid2.get());
return fixed_to_float(curr[(y + 1) * stride + (x + 1)]);
}
int16_t WaveSimulation2D_Real::geti16(size_t x, size_t y) const {
if (x >= width || y >= height) {
FASTLED_WARN("Out of range: " << x << ", " << y);
return 0;
}
const int16_t *curr = (whichGrid == 0 ? grid1.get() : grid2.get());
return curr[(y + 1) * stride + (x + 1)];
}
int16_t WaveSimulation2D_Real::geti16Previous(size_t x, size_t y) const {
if (x >= width || y >= height) {
FASTLED_WARN("Out of range: " << x << ", " << y);
return 0;
}
const int16_t *prev = (whichGrid == 0 ? grid2.get() : grid1.get());
return prev[(y + 1) * stride + (x + 1)];
}
bool WaveSimulation2D_Real::has(size_t x, size_t y) const {
return (x < width && y < height);
}
void WaveSimulation2D_Real::setf(size_t x, size_t y, float value) {
int16_t v = float_to_fixed(value);
return seti16(x, y, v);
}
void WaveSimulation2D_Real::seti16(size_t x, size_t y, int16_t value) {
if (x >= width || y >= height) {
FASTLED_WARN("Out of range: " << x << ", " << y);
return;
}
int16_t *curr = (whichGrid == 0 ? grid1.get() : grid2.get());
curr[(y + 1) * stride + (x + 1)] = value;
}
void WaveSimulation2D_Real::update() {
int16_t *curr = (whichGrid == 0 ? grid1.get() : grid2.get());
int16_t *next = (whichGrid == 0 ? grid2.get() : grid1.get());
// Update horizontal boundaries.
for (size_t j = 0; j < height + 2; ++j) {
curr[j * stride + 0] = curr[j * stride + 1];
curr[j * stride + (width + 1)] = curr[j * stride + width];
}
// Update vertical boundaries.
for (size_t i = 0; i < width + 2; ++i) {
curr[0 * stride + i] = curr[1 * stride + i];
curr[(height + 1) * stride + i] = curr[height * stride + i];
}
// Compute the dampening factor as an integer: 2^(dampening).
int32_t dampening_factor = 1 << mDampening; // e.g., 6 -> 64
int32_t mCourantSq32 = static_cast<int32_t>(mCourantSq);
// Update each inner cell.
for (size_t j = 1; j <= height; ++j) {
for (size_t i = 1; i <= width; ++i) {
int index = j * stride + i;
// Laplacian: sum of four neighbors minus 4 times the center.
int32_t laplacian = (int32_t)curr[index + 1] + curr[index - 1] +
curr[index + stride] + curr[index - stride] -
((int32_t)curr[index] << 2);
// Compute the new value:
// f = - next[index] + 2 * curr[index] + mCourantSq * laplacian
// The multiplication is in Q15, so we shift right by 15.
int32_t term = (mCourantSq32 * laplacian) >> 15;
int32_t f =
-(int32_t)next[index] + ((int32_t)curr[index] << 1) + term;
// Apply damping:
f = f - (f / dampening_factor);
// Clamp f to the Q15 range.
if (f > 32767)
f = 32767;
else if (f < -32768)
f = -32768;
next[index] = (int16_t)f;
}
}
if (mHalfDuplex) {
// Set negative values to zero.
for (size_t j = 1; j <= height; ++j) {
for (size_t i = 1; i <= width; ++i) {
int index = j * stride + i;
if (next[index] < 0) {
next[index] = 0;
}
}
}
}
// Swap the roles of the grids.
whichGrid ^= 1;
}
} // namespace fl

View File

@@ -0,0 +1,203 @@
/*
Wave simulation classes for 1D and 2D simulations. This is called _Real because
there is a one to one mapping between the simulation and the LED grid. For
flexible super sampling see wave_simluation.h.
Based on works and code by Shawn Silverman.
*/
#pragma once
#include <stdint.h>
#include "fl/math_macros.h" // if needed for MAX/MIN macros
#include "fl/namespace.h"
#include "fl/scoped_ptr.h"
#include "fl/warn.h"
#include "fl/ptr.h"
#include "fl/supersample.h"
#include "fl/xymap.h"
#include "fx/fx.h"
#include "fx/fx2d.h"
namespace fl {
namespace wave_detail {
int16_t float_to_fixed(float f);
// Convert fixed Q15 to float.
float fixed_to_float(int16_t f);
} // namespace wave_detail
class WaveSimulation1D_Real {
public:
// Constructor:
// - length: inner simulation grid length (excluding the 2 boundary cells).
// - speed: simulation speed (in float, will be stored in Q15).
// - dampening: exponent so that the effective damping factor is
// 2^(dampening).
WaveSimulation1D_Real(uint32_t length, float speed = 0.16f,
int dampening = 6);
~WaveSimulation1D_Real() = default;
// Set simulation speed (courant parameter) using a float.
void setSpeed(float something);
// Set the dampening exponent (effective damping factor is 2^(dampening)).
void setDampening(int damp);
// Get the current dampening exponent.
int getDampenening() const;
// Get the simulation speed as a float.
float getSpeed() const;
void setHalfDuplex(bool on) { mHalfDuplex = on; }
bool getHalfDuplex() const { return mHalfDuplex; }
// Get the simulation value at the inner grid cell x (converted to float in
// the range [-1.0, 1.0]).
float getf(size_t x) const;
int16_t geti16(size_t x) const;
int16_t geti16Previous(size_t x) const;
int8_t geti8(size_t x) const { return static_cast<int8_t>(geti16(x) >> 8); }
// If mHalfDuplex is set then the the values are adjusted so that negative
// values will instead be represented by zero.
uint8_t getu8(size_t x) const {
int16_t value = geti16(x);
// Rebase the range from [-32768, 32767] to [0, 65535] then extract the
// upper 8 bits.
// return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768))
// >>
// 8);
if (mHalfDuplex) {
uint16_t v2 = static_cast<uint16_t>(value);
v2 *= 2;
return static_cast<uint8_t>(v2 >> 8);
} else {
return static_cast<uint8_t>(
((static_cast<uint16_t>(value) + 32768)) >> 8);
}
}
// Returns whether x is within the inner grid bounds.
bool has(size_t x) const;
// Set the value at grid cell x (expected range: [-1.0, 1.0]); the value is
// stored in Q15.
void set(size_t x, float value);
// Advance the simulation one time step.
void update();
private:
uint32_t length; // Length of the inner simulation grid.
// Two grids stored in fixed Q15 format, each with length+2 entries
// (including boundary cells).
fl::scoped_array<int16_t> grid1;
fl::scoped_array<int16_t> grid2;
size_t whichGrid; // Indicates the active grid (0 or 1).
int16_t mCourantSq; // Simulation speed (courant squared) stored in Q15.
int mDampenening; // Dampening exponent (damping factor = 2^(mDampenening)).
bool mHalfDuplex =
true; // Flag to restrict values to positive range during update.
};
class WaveSimulation2D_Real {
public:
// Constructor: Initializes the simulation with inner grid size (W x H).
// The grid dimensions include a 1-cell border on each side.
// Here, 'speed' is specified as a float (converted to fixed Q15)
// and 'dampening' is given as an exponent so that the damping factor is
// 2^dampening.
WaveSimulation2D_Real(uint32_t W, uint32_t H, float speed = 0.16f,
float dampening = 6.0f);
~WaveSimulation2D_Real() = default;
// Set the simulation speed (courantSq) using a float value.
void setSpeed(float something);
// Set the dampening factor exponent.
// The dampening factor used is 2^(dampening).
void setDampening(int damp);
// Get the current dampening exponent.
int getDampenening() const;
// Get the simulation speed as a float (converted from fixed Q15).
float getSpeed() const;
// Return the value at an inner grid cell (x,y), converted to float.
// The value returned is in the range [-1.0, 1.0].
float getf(size_t x, size_t y) const;
// Return the value at an inner grid cell (x,y) as a fixed Q15 integer
// in the range [-32768, 32767].
int16_t geti16(size_t x, size_t y) const;
int16_t geti16Previous(size_t x, size_t y) const;
int8_t geti8(size_t x, size_t y) const {
return static_cast<int8_t>(geti16(x, y) >> 8);
}
uint8_t getu8(size_t x, size_t y) const {
int16_t value = geti16(x, y);
// Rebase the range from [-32768, 32767] to [0, 65535] then extract the
// upper 8 bits.
// return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768))
// >>
// 8);
if (mHalfDuplex) {
uint16_t v2 = static_cast<uint16_t>(value);
v2 *= 2;
return static_cast<uint8_t>(v2 >> 8);
} else {
return static_cast<uint8_t>(
((static_cast<uint16_t>(value) + 32768)) >> 8);
}
}
// Check if (x,y) is within the inner grid.
bool has(size_t x, size_t y) const;
// Set the value at an inner grid cell using a float;
// the value is stored in fixed Q15 format.
// value shoudl be between -1.0 and 1.0.
void setf(size_t x, size_t y, float value);
void seti16(size_t x, size_t y, int16_t value);
void setHalfDuplex(bool on) { mHalfDuplex = on; }
bool getHalfDuplex() const { return mHalfDuplex; }
// Advance the simulation one time step using fixed-point arithmetic.
void update();
uint32_t getWidth() const { return width; }
uint32_t getHeight() const { return height; }
private:
uint32_t width; // Width of the inner grid.
uint32_t height; // Height of the inner grid.
uint32_t stride; // Row length (width + 2 for the borders).
// Two separate grids stored in fixed Q15 format.
fl::scoped_array<int16_t> grid1;
fl::scoped_array<int16_t> grid2;
size_t whichGrid; // Indicates the active grid (0 or 1).
int16_t mCourantSq; // Fixed speed parameter in Q15.
int mDampening; // Dampening exponent; used as 2^(dampening).
bool mHalfDuplex =
true; // Flag to restrict values to positive range during update.
};
} // namespace fl

Some files were not shown because too many files have changed in this diff Show More