imported from "final" folder

This commit is contained in:
2025-11-28 12:12:50 +01:00
parent f9288986cf
commit ff8e725b35
1061 changed files with 225150 additions and 96 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
accde1a8736a436d123393d8d11a0e7301cdf78d

View File

@@ -0,0 +1,23 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
*~

View File

@@ -0,0 +1 @@
{"type": "library", "name": "ArtnetWifi", "version": "1.6.2", "spec": {"owner": "rstephan", "id": 6328, "name": "ArtnetWifi", "requirements": null, "uri": null}}

View File

@@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2014 Nathanaël Lécaudé
https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811
Copyright (c) 2016 Stephan Ruloff
https://github.com/rstephan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,91 @@
# ArtnetWifi
[![arduino-library-badge](https://www.ardu-badge.com/badge/ArtnetWifi.svg?)](https://www.ardu-badge.com/ArtnetWifi)
An Art-Net library for Wifi-Arduino's. Tested on ESP8266, ESP32, Pi Pico W, WiFi101 (e.g. MKR1000) and WiFiNINA (e.g. NANO 33 IoT) devices.
Note: this library assumes you are using a wifi module.
Based on https://github.com/natcl/Artnet [master](https://github.com/natcl/Artnet/archive/master.zip)
## Installation
### Arduino IDE
Navigate to **Sketch** -> **Include Library** -> **Manage Libraries...**,
then search for `ArtnetWifi` and the library will show up. Click **Install** and the library is ready to use.
### PlatformIO Core (CLI)
```
$ pio init --board nodemcuv2
$ pio lib install artnetwifi
```
### Manual
Place this in your `~/Documents/Arduino/libraries` folder.
## Examples
Different examples are provided, here is a summary of what each example does.
### ArtnetWifiDebug
Simple test for WiFi, serial and Art-Net.
Example output (Serial Monitor, 115200 Baud):
```
DMX: Univ: 0, Seq: 0, Data (48): 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...
```
If this example is not working, don't try anything else!
### ArtnetWifiDebug2 (ArtnetWifiDebug with C++11 style)
See **ArtnetWifiDebug**.
**Note:** Not all controllers support this type of code!
### ArtnetWifiFastLED
This example will receive multiple universes via Art-Net and control a strip of WS2812 LEDs via the [FastLED library](https://github.com/FastLED/FastLED). It is similar to the NeoPixel example but it will work on the ESP32 and the ESP8266 controller as well.
### ArtnetWifiNeoPixel
This example will receive multiple universes via Art-Net and control a strip of WS2811 LEDs via Adafruit's [NeoPixel library](https://github.com/adafruit/Adafruit_NeoPixel).
### ArtnetWifiTransmit
This is a simple transmitter. Send 3 byte over into the Art-Net, to make a RGB light ramp-up in white.
### Notes
The examples `FastLED` and `NeoPixel` can utilize many universes to light up hundreds of LEDs.
Normaly Art-Net frame can handle 512 bytes. Divide this by 3 colors, so a single universe can
have 170.66 LEDs. For easy access, only 170 LEDs are used. The last 2 byte per universe are "lost".
**Example:** 240 LEDs, 720 Byte, 2 Universes
**Universe "1"**
|Byte | 1| 2| 3|...|508|509|510|511|512|
|:----|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|Color| R| G| B|...| R| G| B| x | x |
|LED | 1| 1| 1|...|170|170|170| | |
**Universe "2"**
|Byte | 1| 2| 3|...|208|209|210|
|:----|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|Color| R| G| B|...| R| G| B|
|LED |171|171|171|...|240|240|240|
*You only have to send 510 byte DMX-data per frame. Extra byte(s) at the end will be ignored!*
# Art-Net
Art-Net(tm) is a trademark of Artistic Licence Holdings Ltd. The Art-Net protocol and associated documentation is copyright Artistic Licence Holdings Ltd.
[Art-Net](http://www.artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf)

View File

@@ -0,0 +1,95 @@
/*
Example, transmit all received ArtNet messages (DMX) out of the serial port in plain text.
Stephan Ruloff 2016,2019
https://github.com/rstephan
*/
#include <ArtnetWifi.h>
#include <Arduino.h>
//Wifi settings
const char* ssid = "ssid";
const char* password = "pAsSwOrD";
WiFiUDP UdpSend;
ArtnetWifi artnet;
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
bool state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false;
break;
}
i++;
}
if (state) {
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(IPAddress(WiFi.localIP()));
} else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
bool tail = false;
Serial.print("DMX: Univ: ");
Serial.print(universe, DEC);
Serial.print(", Seq: ");
Serial.print(sequence, DEC);
Serial.print(", Data (");
Serial.print(length, DEC);
Serial.print("): ");
if (length > 16) {
length = 16;
tail = true;
}
// send out the buffer
for (uint16_t i = 0; i < length; i++)
{
Serial.print(data[i], HEX);
Serial.print(" ");
}
if (tail) {
Serial.print("...");
}
Serial.println();
}
void setup()
{
// set-up serial for debug output
Serial.begin(115200);
ConnectWifi();
// this will be called for each packet received
artnet.setArtDmxCallback(onDmxFrame);
artnet.begin();
}
void loop()
{
// we call the read function inside the loop
artnet.read();
}

View File

@@ -0,0 +1,96 @@
/*
Example, transmit all received ArtNet messages (Op-Code DMX) out of the
serial port in plain text.
Function pointer with the C++11 function-object "std::function".
Stephan Ruloff 2019
https://github.com/rstephan/ArtnetWifi
*/
#if defined(ARDUINO_AVR_UNO_WIFI_REV2)
#error "No C++11 support! Use a more powerful controller or the other 'ArtnetWifiDebug' example, sorry."
#endif
#include <ArtnetWifi.h>
#include <Arduino.h>
//Wifi settings
const char* ssid = "ssid";
const char* password = "pAsSwOrD";
WiFiUDP UdpSend;
ArtnetWifi artnet;
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
bool state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false;
break;
}
i++;
}
if (state) {
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
void setup()
{
// set-up serial for debug output
Serial.begin(115200);
ConnectWifi();
// this will be called for each packet received
artnet.setArtDmxFunc([](DMX_FUNC_PARAM){
bool tail = false;
Serial.print("DMX: Univ: ");
Serial.print(universe, DEC);
Serial.print(", Seq: ");
Serial.print(sequence, DEC);
Serial.print(", Data (");
Serial.print(length, DEC);
Serial.print("): ");
if (length > 16) {
length = 16;
tail = true;
}
// send out the buffer
for (uint16_t i = 0; i < length; i++) {
Serial.print(data[i], HEX);
Serial.print(" ");
}
if (tail) {
Serial.print("...");
}
Serial.println();
});
artnet.begin();
}
void loop()
{
// we call the read function inside the loop
artnet.read();
}

View File

@@ -0,0 +1,165 @@
/*
This example will receive multiple universes via Art-Net and control a strip of
WS2812 LEDs via the FastLED library: https://github.com/FastLED/FastLED
This example may be copied under the terms of the MIT license, see the LICENSE file for details
*/
#include <ArtnetWifi.h>
#include <Arduino.h>
#include <FastLED.h>
// Wifi settings
const char* ssid = "ssid";
const char* password = "pAsSwOrD";
// LED settings
const int numLeds = 8; // CHANGE FOR YOUR SETUP
const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin = 2;
CRGB leds[numLeds];
// Art-Net settings
ArtnetWifi artnet;
const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0.
// Check if we got all universes
const int maxUniverses = numberOfChannels / 510 + ((numberOfChannels % 510) ? 1 : 0);
bool universesReceived[maxUniverses];
bool sendFrame = 1;
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
bool state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
if (i > 20)
{
state = false;
break;
}
i++;
}
if (state)
{
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
else
{
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
void initTest()
{
for (int i = 0 ; i < numLeds ; i++)
{
leds[i] = CRGB(127, 0, 0);
}
FastLED.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
{
leds[i] = CRGB(0, 127, 0);
}
FastLED.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
{
leds[i] = CRGB(0, 0, 127);
}
FastLED.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
{
leds[i] = CRGB(0, 0, 0);
}
FastLED.show();
}
void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
sendFrame = 1;
// set brightness of the whole strip
if (universe == 15)
{
FastLED.setBrightness(data[0]);
FastLED.show();
}
// range check
if (universe < startUniverse)
{
return;
}
uint8_t index = universe - startUniverse;
if (index >= maxUniverses)
{
return;
}
// Store which universe has got in
universesReceived[index] = true;
for (int i = 0 ; i < maxUniverses ; i++)
{
if (!universesReceived[i])
{
sendFrame = 0;
break;
}
}
// read universe and put into the right part of the display buffer
for (int i = 0; i < length / 3; i++)
{
int led = i + (index * 170);
if (led < numLeds)
{
leds[led] = CRGB(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
}
}
if (sendFrame)
{
FastLED.show();
// Reset universeReceived to 0
memset(universesReceived, 0, maxUniverses);
}
}
void setup()
{
Serial.begin(115200);
ConnectWifi();
artnet.begin();
FastLED.addLeds<WS2812, dataPin, GRB>(leds, numLeds);
initTest();
memset(universesReceived, 0, maxUniverses);
// this will be called for each packet received
artnet.setArtDmxCallback(onDmxFrame);
}
void loop()
{
// we call the read function inside the loop
artnet.read();
}

View File

@@ -0,0 +1,141 @@
/*
This example will receive multiple universes via Artnet and control a strip of ws2811 leds via
Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
This example may be copied under the terms of the MIT license, see the LICENSE file for details
*/
#include <ArtnetWifi.h>
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
//Wifi settings
const char* ssid = "ssid";
const char* password = "pAsSwOrD";
// Neopixel settings
const int numLeds = 240; // change for your setup
const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin = 2;
Adafruit_NeoPixel leds = Adafruit_NeoPixel(numLeds, dataPin, NEO_GRB + NEO_KHZ800);
// Artnet settings
ArtnetWifi artnet;
const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0.
// Check if we got all universes
const int maxUniverses = numberOfChannels / 512 + ((numberOfChannels % 512) ? 1 : 0);
bool universesReceived[maxUniverses];
bool sendFrame = 1;
int previousDataLength = 0;
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
bool state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false;
break;
}
i++;
}
if (state){
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
void initTest()
{
for (int i = 0 ; i < numLeds ; i++)
leds.setPixelColor(i, 127, 0, 0);
leds.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
leds.setPixelColor(i, 0, 127, 0);
leds.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
leds.setPixelColor(i, 0, 0, 127);
leds.show();
delay(500);
for (int i = 0 ; i < numLeds ; i++)
leds.setPixelColor(i, 0, 0, 0);
leds.show();
}
void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
sendFrame = 1;
// set brightness of the whole strip
if (universe == 15)
{
leds.setBrightness(data[0]);
leds.show();
}
// Store which universe has got in
if ((universe - startUniverse) < maxUniverses)
universesReceived[universe - startUniverse] = 1;
for (int i = 0 ; i < maxUniverses ; i++)
{
if (universesReceived[i] == 0)
{
//Serial.println("Broke");
sendFrame = 0;
break;
}
}
// read universe and put into the right part of the display buffer
for (int i = 0; i < length / 3; i++)
{
int led = i + (universe - startUniverse) * (previousDataLength / 3);
if (led < numLeds)
leds.setPixelColor(led, data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
}
previousDataLength = length;
if (sendFrame)
{
leds.show();
// Reset universeReceived to 0
memset(universesReceived, 0, maxUniverses);
}
}
void setup()
{
Serial.begin(115200);
ConnectWifi();
artnet.begin();
leds.begin();
initTest();
// this will be called for each packet received
artnet.setArtDmxCallback(onDmxFrame);
}
void loop()
{
// we call the read function inside the loop
artnet.read();
}

View File

@@ -0,0 +1,75 @@
/*
This example will transmit a universe via Art-Net into the Network.
This example may be copied under the terms of the MIT license, see the LICENSE file for details
*/
#include <ArtnetWifi.h>
#include <Arduino.h>
//Wifi settings
const char* ssid = "ssid"; // CHANGE FOR YOUR SETUP
const char* password = "pAsSwOrD"; // CHANGE FOR YOUR SETUP
// Artnet settings
ArtnetWifi artnet;
const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0.
const char host[] = "2.1.1.1"; // CHANGE FOR YOUR SETUP your destination
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
bool state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false;
break;
}
i++;
}
if (state){
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
void setup()
{
Serial.begin(115200);
ConnectWifi();
artnet.begin(host);
artnet.setLength(3);
artnet.setUniverse(startUniverse);
}
void loop()
{
uint8_t i;
uint8_t j;
// set the first 3 byte to all the same value. A RGB lamp will show a ramp-up white.
for (j = 0; j < 255; j++) {
for (i = 0; i < 3; i++) {
artnet.setByte(i, j);
}
// send out the Art-Net DMX data
artnet.write();
delay(100);
}
}

View File

@@ -0,0 +1,21 @@
# Datatypes
ArtnetWifi KEYWORD1
# Methods
begin KEYWORD2
read KEYWORD2
write KEYWORD2
setByte KEYWORD2
printPacketHeader KEYWORD2
printPacketContent KEYWORD2
getDmxFrame KEYWORD2
getOpcode KEYWORD2
getSequence KEYWORD2
getUniverse KEYWORD2
setUniverse KEYWORD2
setPhysical KEYWORD2
getLength KEYWORD2
setLength KEYWORD2
setArtDmxCallback KEYWORD2

View File

@@ -0,0 +1,9 @@
name=ArtnetWifi
version=1.6.2
author=Nathanaël Lécaudé,Stephan Ruloff
maintainer=Stephan Ruloff <stephan.ruloff@gmail.com>
sentence=ArtNet with the ESP8266, ESP32, RP2040 and more.
paragraph=Send and receive Art-Net frames using WiFi. Tested on ESP8266, ESP32, Pi Pico W, AmebaD, WiFi101 and WiFiNINA devices.
category=Communication
url=https://github.com/rstephan/ArtnetWifi
architectures=esp8266,esp32,samd,rp2040,megaavr,AmebaD

View File

@@ -0,0 +1,169 @@
/*The MIT License (MIT)
Copyright (c) 2014 Nathanaël Lécaudé
https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811
Copyright (c) 2016,2019 Stephan Ruloff
https://github.com/rstephan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <ArtnetWifi.h>
const char ArtnetWifi::artnetId[] = ART_NET_ID;
ArtnetWifi::ArtnetWifi() : artDmxCallback(nullptr) {}
void ArtnetWifi::begin(String hostname)
{
Udp.begin(ART_NET_PORT);
host = hostname;
sequence = 1;
physical = 0;
}
void ArtnetWifi::stop(void)
{
Udp.stop();
}
uint16_t ArtnetWifi::read(void)
{
packetSize = Udp.parsePacket();
if (packetSize <= MAX_BUFFER_ARTNET && packetSize > 0)
{
senderIp = Udp.remoteIP();
Udp.read(artnetPacket, MAX_BUFFER_ARTNET);
// Check that packetID is "Art-Net" else ignore
if (memcmp(artnetPacket, artnetId, sizeof(artnetId)) != 0) {
return 0;
}
opcode = artnetPacket[8] | artnetPacket[9] << 8;
if (opcode == ART_DMX)
{
sequence = artnetPacket[12];
incomingUniverse = artnetPacket[14] | artnetPacket[15] << 8;
dmxDataLength = artnetPacket[17] | artnetPacket[16] << 8;
if (artDmxCallback) (*artDmxCallback)(incomingUniverse, dmxDataLength, sequence, artnetPacket + ART_DMX_START);
#if !defined(ARDUINO_AVR_UNO_WIFI_REV2)
if (artDmxFunc) {
artDmxFunc(incomingUniverse, dmxDataLength, sequence, artnetPacket + ART_DMX_START);
}
#endif
return ART_DMX;
}
if (opcode == ART_POLL)
{
return ART_POLL;
}
if (opcode == ART_SYNC)
{
return ART_SYNC;
}
}
return 0;
}
uint16_t ArtnetWifi::makePacket(void)
{
uint16_t len;
uint16_t version;
memcpy(artnetPacket, artnetId, sizeof(artnetId));
opcode = ART_DMX;
artnetPacket[8] = opcode;
artnetPacket[9] = opcode >> 8;
version = 14;
artnetPacket[11] = version;
artnetPacket[10] = version >> 8;
artnetPacket[12] = sequence;
sequence++;
if (!sequence) {
sequence = 1;
}
artnetPacket[13] = physical;
artnetPacket[14] = outgoingUniverse;
artnetPacket[15] = outgoingUniverse >> 8;
len = dmxDataLength + (dmxDataLength % 2); // make a even number
artnetPacket[17] = len;
artnetPacket[16] = len >> 8;
return len;
}
int ArtnetWifi::write(void)
{
uint16_t len;
len = makePacket();
Udp.beginPacket(host.c_str(), ART_NET_PORT);
Udp.write(artnetPacket, ART_DMX_START + len);
return Udp.endPacket();
}
int ArtnetWifi::write(IPAddress ip)
{
uint16_t len;
len = makePacket();
Udp.beginPacket(ip, ART_NET_PORT);
Udp.write(artnetPacket, ART_DMX_START + len);
return Udp.endPacket();
}
void ArtnetWifi::setByte(uint16_t pos, uint8_t value)
{
if (pos > 512) {
return;
}
artnetPacket[ART_DMX_START + pos] = value;
}
void ArtnetWifi::printPacketHeader(void)
{
Serial.print("packet size = ");
Serial.print(packetSize);
Serial.print("\topcode = ");
Serial.print(opcode, HEX);
Serial.print("\tuniverse number = ");
Serial.print(incomingUniverse);
Serial.print("\tdata length = ");
Serial.print(dmxDataLength);
Serial.print("\tsequence n0. = ");
Serial.println(sequence);
}
void ArtnetWifi::printPacketContent(void)
{
for (uint16_t i = ART_DMX_START ; i < dmxDataLength ; i++){
Serial.print(artnetPacket[i], DEC);
Serial.print(" ");
}
Serial.println('\n');
}

View File

@@ -0,0 +1,173 @@
/*The MIT License (MIT)
Copyright (c) 2014 Nathanaël Lécaudé
https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811
Copyright (c) 2016,2019 Stephan Ruloff
https://github.com/rstephan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef ARTNET_WIFI_H
#define ARTNET_WIFI_H
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_RASPBERRY_PI_PICO_2W)
#include <WiFi.h>
#include <functional>
#elif defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <functional>
#elif defined(ARDUINO_ARCH_SAMD)
#if defined(ARDUINO_SAMD_MKR1000)
#include <WiFi101.h>
#else
#include <WiFiNINA.h>
#endif
#include <functional>
#elif defined(ARDUINO_AVR_UNO_WIFI_REV2)
#include <WiFiNINA.h>
#elif defined(ARDUINO_ARCH_AMEBAD)
#include <WiFi.h>
#include <functional>
#else
#error "Architecture not supported!"
#endif
#include <WiFiUdp.h>
// UDP specific
#define ART_NET_PORT 6454
// Opcodes
#define ART_POLL 0x2000
#define ART_DMX 0x5000
#define ART_SYNC 0x5200
// Buffers
#define MAX_BUFFER_ARTNET 530
// Packet
#define ART_NET_ID "Art-Net"
#define ART_DMX_START 18
#define DMX_FUNC_PARAM uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data
#if !defined(ARDUINO_AVR_UNO_WIFI_REV2)
typedef std::function <void (DMX_FUNC_PARAM)> StdFuncDmx_t;
#endif
class ArtnetWifi
{
public:
ArtnetWifi();
void begin(String hostname = "");
uint16_t read(void);
/* returns 1 for Ok, or 0 on problem */
int write(void);
int write(IPAddress ip);
void setByte(uint16_t pos, uint8_t value);
void printPacketHeader(void);
void printPacketContent(void);
void stop(void);
// Return a pointer to the start of the DMX data
inline uint8_t* getDmxFrame(void)
{
return artnetPacket + ART_DMX_START;
}
inline uint16_t getOpcode(void)
{
return opcode;
}
inline uint8_t getSequence(void)
{
return sequence;
}
inline uint16_t getUniverse(void)
{
return incomingUniverse;
}
inline void setUniverse(uint16_t universe)
{
outgoingUniverse = universe;
}
inline void setPhysical(uint8_t port)
{
physical = port;
}
[[deprecated]]
inline void setPhisical(uint8_t port)
{
setPhysical(port);
}
inline uint16_t getLength(void)
{
return dmxDataLength;
}
inline void setLength(uint16_t len)
{
dmxDataLength = len;
}
inline void setArtDmxCallback(void (*fptr)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data))
{
artDmxCallback = fptr;
}
#if !defined(ARDUINO_AVR_UNO_WIFI_REV2)
inline void setArtDmxFunc(StdFuncDmx_t func)
{
artDmxFunc = func;
}
#endif
inline IPAddress& getSenderIp()
{
return senderIp;
}
private:
uint16_t makePacket(void);
WiFiUDP Udp;
String host;
uint8_t artnetPacket[MAX_BUFFER_ARTNET];
uint16_t packetSize;
uint16_t opcode;
uint8_t sequence;
uint8_t physical;
uint16_t incomingUniverse;
uint16_t outgoingUniverse;
uint16_t dmxDataLength;
void (*artDmxCallback)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data);
#if !defined(ARDUINO_AVR_UNO_WIFI_REV2)
StdFuncDmx_t artDmxFunc;
#endif
static const char artnetId[];
IPAddress senderIp;
};
#endif

View File

@@ -0,0 +1 @@
{"type": "library", "name": "FastLED", "version": "3.10.3", "spec": {"owner": "fastled", "id": 126, "name": "FastLED", "requirements": null, "uri": null}}

View File

@@ -0,0 +1,66 @@
# Advanced Development with FastLED
![perpetualmaniac_neo_giving_two_pills _one_of_them_is_black_and__4b145870-9ead-4976-b031-f4df3f2bfbe1](https://github.com/user-attachments/assets/9bba6113-688f-469f-8b51-bcb4fea910e5)
## GDB On Unit Tests
Yes, we have step through debugging with FastLED.
* VSCode
* Install Plugin: GDB Debugger - Beyond
* Navigate to one of the tests in `tests/` and open in
* Hit `F5`
If the Python Debugger pops up, then manually switch the VSCode debugger using `Launch(gdb)`
![image](https://github.com/user-attachments/assets/c1246803-df4b-4583-8c11-8243c084afc5)
## Enabling 3-second compile times using our `web-compiler`
* You must have `docker` installed for fastest compile times. It's free.
* `cd <FASTLED FOLDER>`
* `pip install fastled`
* `fastled examples/Blink/Blink.ino`
* Cpp changes to the fastled source can be compiled by the live fastled compiler.
## Testing your changes
Most of this is in the basic CONTRIBUTING.md guide. But as a reminder
* Unit Testing: `./test`
* Linting: `./lint`
* Compiling on platforms `./compile uno,teensy41,esp32s3 --examples Blink,Apa102HD`
## Enabling AI coding
`aider.chat` is available for advanced and high velocity coding with FastLED. To use it, have your open-ai or Anthropic api key ready. It's recommended to use Anthropic as it's performance is much better than Open-AI for coding.
At the root of the project type:
`./ai` and follow the prompts. Once the key is installed you will get a prompt that looks like this:
```bash
architect>
```
There are two modes to use this AI, a watch mode which watches your files for changes and launches automatically, and a slow way which you add target files then instruct it to make changes:
* watch mode (best for implimenting a function or two)
* Edit any file in the repo. Add a comment with `AI!` at the end. The ai will see this and start implementing what you just typed.
* Example: Edit `src/fl/vector.h` and put in a comment `// Add more comments AI!`, then say yes to the changes in the prompt.
* Slow mode (much better for bigger changes across the file or several)
* While you are in the `architect> ` prompt you will add a file to the chat
* `/add src/fl/vector.h`
* Now tell the AI what you want it to do, and it will do it.
* Making the AI fix it's own problems it introduced.
* At the AI command prompt, have it run the following
* Linux/Mac: `/run ./test`
* On Windows: `/run uv run test.py`
* After the test concludes, the AI will ask you if you want to add the output back into the chat, agree to it then let it try to correct it's mistakes.
Every single time you do a change, make sure and thoroughly check it. I recommend VSCodes built in git diff tool.
Although the AI is pretty amazing, it will inject entropy into your code and this is the source of a lot of problems. So watch all changes it makes very thoroughly. Under almost all circumstances you will have to revert unnecessary changes (like comments) line by line.

View File

@@ -0,0 +1,73 @@
# Special Notes on APA102 and the 'High Definition' Mode in FastLED
The APA102 LED driver includes a 5-bit per-LED brightness component. Previously, this feature was not fully utilized, except through a workaround that defined a global brightness affecting all LEDs uniformly rather than individually.
In FastLED the APA102 chipset will have extra resolution in comparison to the WS2812 RGB8 mode.
There are two modes:
* APA102 "Regular Mode"
* Has enhanced color resolution when using the "global brightness" factor
* APA102HD Mode
* Applies automatic gamma correction at the driver level using "pseudo 13-bit" color mixing.
**APA102HD Mode**
[example: examples/APA102HD](examples/Apa102HD/)
By introducing a 5-bit gamma bit-shift algorithm, we now effectively leverage this per-LED brightness control. Faced with the decision to either rewrite the entire `CRGB` library to expose the 5-bit brightness—including adaptations for formats like RGBW—or to retain the existing RGB8 format used by FastLED and implement the enhancement at the driver level, the latter option was chosen. This approach avoids widespread changes and maintains compatibility; if RGB8 suffices for game development, it is adequate for LED development as well.
The term "Pseudo-13-bit" arises because the additional resolution becomes significant only when all color components are at low values. For example, colors like `CRGB(255, 255, 254)` or `CRGB(255, 1, 1)` do not benefit from increased resolution due to the dominance of the brighter components. However, in low-light conditions with colors such as `CRGB(8, 8, 8)`, where the maximum component value is low, the pseudo-13-bit algorithm significantly enhances resolution—precisely where increased resolution is most desired.
Gamma correction is applied to preserve the RGB8 format and because future LEDs are expected to support gamma correction inherently. In game development, the 0-255 color values are based on the gamma scale rather than the linear power scale. LEDs like the WS2812 operate on a linear power scale, which results in washed-out, undersaturated colors when displaying captured video directly. Implementing software gamma correction for RGB8 severely reduces color resolution.
To address this, an internal gamma scale mapping is applied:
```
RGB8 → RGB16 + 5-bit gamma → RGB8 + 5-bit gamma
```
During the conversion back to RGB8, the brightness from the 5-bit gamma is optimally distributed using a closed-form mathematical algorithm. Rather than the iterative bit-shifting approach used in earlier versions, the current implementation uses a sophisticated quantization and scaling method that provides superior accuracy.
**Closed-Form Algorithm (Current Implementation)**
The algorithm works by:
1. **Finding the maximum component**: Determines which RGB component requires the highest brightness level
2. **Quantizing to 5-bit scale**: Computes the optimal 5-bit brightness value that accommodates the maximum component
3. **Applying pre-computed scaling**: Uses a lookup table of precise scaling factors to convert the 16-bit gamma-corrected values back to 8-bit RGB components
The key insight is the quantization formula:
```
scale = (max_component + (2047 - (max_component >> 5))) >> 11
```
This ensures the 5-bit brightness value is always sufficient to represent the maximum component while minimizing quantization error.
The scaling factors in the lookup table are computed as:
```
bright_scale[i] = (i/31) * (255/65536) * 256
```
These pre-computed values eliminate floating-point arithmetic while maintaining precision. The final RGB values are calculated with proper rounding:
```
rgb_out = (rgb16 * scale_factor + 0x808000) >> 24
```
**Algorithm Performance**
This closed-form approach delivers exceptional accuracy:
- **Worst-case error**: 0.2% over the full 16-bit input range
- **Average error**: 0.05% across the range
- **Perfect match**: to floating-point reference implementation
By eliminating iterative bit-shifting, this method also provides consistent performance regardless of input values and significantly reduces code size on embedded platforms.
**Version History**
In version 3.9.0, the algorithm was completely rewritten to function natively on 8-bit controllers like the `__AVR__` chipsets without significant performance loss. The current closed-form implementation replaced the previous iterative approach, providing both better accuracy and smaller code footprint.
**Further Enhancements in Version 3.9.0**
Additionally, version 3.9.0 separated the color temperature from the global brightness scale. Before this update, global brightness was pre-mixed with the component scales—a method suitable for the WS2812's RGB8 format but not for the APA102's RGB8 plus 5-bit brightness. The update saw the global brightness and color scales separated for non-AVR chipsets. While the WS2812 continues to use pre-mixed values for performance reasons on AVR chipsets, the APA102 now performs component mixing within the "pseudo-13-bit space."
Although APA102HD mode offers the highest dynamic range, the standard APA102 mode also benefits from increased resolution when adjusting global brightness. In this mode, instead of pre-mixing scales and multiplying them against each `CRGB` value, the global brightness is applied to the 5-bit brightness component, and only the color scales are multiplied against the `CRGB` values. This approach is superior because each component of the color scale typically exceeds 127, providing ample high-order bits to preserve color information.

View File

@@ -0,0 +1,168 @@
# 🚨🚨🚨 **CRITICAL REQUIREMENTS FOR BACKGROUND AGENTS** 🚨🚨🚨
# ⚠️ **STOP! READ THIS FIRST!** ⚠️
## **MANDATORY TESTING REQUIREMENT - NO EXCEPTIONS!**
### 🔥 **RULE #1: ALWAYS RUN `bash test` BEFORE COMPLETION** 🔥
**THIS IS THE MOST IMPORTANT RULE FOR BACKGROUND AGENTS:**
- **🚨 EVERY BACKGROUND AGENT MUST RUN `bash test` BEFORE SAYING THEY ARE DONE**
- **🚨 NO EXCEPTIONS - THIS IS MANDATORY AND NON-NEGOTIABLE**
- **🚨 IF YOU DON'T RUN `bash test`, YOUR WORK IS INCOMPLETE**
- **🚨 SAYING "DONE" WITHOUT RUNNING `bash test` IS BREAKING THE RULES**
### **WHY THIS IS CRITICAL:**
- The `bash test` command runs the full test suite including unit tests and compilation checks
- It catches breaking changes, compilation errors, and regressions
- It ensures the codebase remains stable and functional
- **WITHOUT THIS, YOU WILL BREAK THE PROJECT**
### **WHAT `bash test` DOES:**
- Runs all C++ unit tests
- Runs Python linting and formatting checks
- Validates code compilation across platforms
- Checks for syntax errors and warnings
- Ensures all examples still compile
### 🚨 **MANDATORY STEPS FOR COMPLETION - FOLLOW EXACTLY:** 🚨
1. **🔥 RUN `bash test` COMMAND**
- Execute: `bash test`
- Wait for ALL tests to complete
- Check that ALL tests PASS
2. **🔥 VERIFY TEST RESULTS**
- If ANY test fails, you MUST fix the issues
- Do NOT ignore warnings or errors
- Do NOT proceed until ALL tests pass
3. **🔥 USE MCP SERVER VALIDATION (RECOMMENDED)**
- Use the `validate_completion` tool from MCP server: `uv run mcp_server.py`
- This tool runs `bash test` and validates results
- Only indicate completion if this tool returns SUCCESS
4. **🔥 ZERO TOLERANCE FOR FAILURES**
- If ANY test fails, the background agent MUST fix the issues
- Do NOT indicate completion with failing tests
- Do NOT ignore test errors or warnings
- Fix the code and re-run `bash test` until ALL tests pass
### **COMPLETION CHECKLIST - MUST COMPLETE ALL ITEMS:**
- [ ] ✅ All code changes have been made
- [ ]**`bash test` has been run and ALL tests PASS**
- [ ] ✅ No compilation errors or warnings remain
- [ ] ✅ MCP server `validate_completion` tool shows SUCCESS (if used)
- [ ] ✅ All test output has been reviewed for any issues
- [ ]**ONLY THEN** indicate task completion
### 🔥 **CONSEQUENCES OF NOT FOLLOWING THIS RULE:** 🔥
- **YOUR CHANGES WILL BREAK THE PROJECT**
- **OTHER DEVELOPERS WILL HAVE TO FIX YOUR MISTAKES**
- **THE CONTINUOUS INTEGRATION SYSTEM WILL FAIL**
- **YOU WILL CAUSE REGRESSIONS AND BUGS**
### 🚨 **FINAL WARNING:** 🚨
**IF YOU ARE A BACKGROUND AGENT AND YOU INDICATE COMPLETION WITHOUT RUNNING `bash test`, YOU HAVE FAILED TO FOLLOW THE MOST BASIC REQUIREMENT OF THIS PROJECT.**
**NO EXCEPTIONS. NO SHORTCUTS. ALWAYS RUN `bash test` FIRST.**
---
## **Examples of CORRECT Background Agent Behavior:**
### ✅ **GOOD Example:**
```
1. Made code changes
2. Ran `bash test`
3. All tests passed
4. Indicated completion: "Task completed successfully. All tests pass."
```
### ❌ **BAD Example:**
```
1. Made code changes
2. Skipped running tests
3. Indicated completion: "Task completed successfully."
```
**^ THIS IS WRONG AND BREAKS THE PROJECT**
### ✅ **GOOD Example with Test Failures:**
```
1. Made code changes
2. Ran `bash test`
3. Some tests failed
4. Fixed the failing code
5. Ran `bash test` again
6. All tests passed
7. Indicated completion: "Task completed successfully. All tests pass."
```
### ❌ **BAD Example with Test Failures:**
```
1. Made code changes
2. Ran `bash test`
3. Some tests failed
4. Ignored failures and indicated completion anyway
```
**^ THIS IS WRONG AND BREAKS THE PROJECT**
---
## **HOW TO RUN `bash test`:**
### **Command:**
```bash
bash test
```
### **Expected Output (Success):**
```
Running C++ tests...
All C++ tests passed ✅
Running Python linting...
All Python checks passed ✅
Running compilation checks...
All compilation checks passed ✅
```
### **What to do if tests fail:**
1. **READ THE ERROR MESSAGES CAREFULLY**
2. **FIX THE CODE THAT'S CAUSING THE FAILURES**
3. **RUN `bash test` AGAIN**
4. **REPEAT UNTIL ALL TESTS PASS**
5. **ONLY THEN INDICATE COMPLETION**
---
## **MCP SERVER VALIDATION TOOL:**
### **How to use:**
```bash
# Start MCP server
uv run mcp_server.py
# Use the validate_completion tool
# This automatically runs `bash test` and validates results
```
### **Benefits:**
- Automatically runs `bash test`
- Provides clear pass/fail results
- Ensures you don't forget to run tests
- **RECOMMENDED FOR ALL BACKGROUND AGENTS**
---
## 🚨 **REMEMBER: THIS IS NOT OPTIONAL** 🚨
**EVERY BACKGROUND AGENT WORKING ON THIS PROJECT MUST:**
1. **RUN `bash test` BEFORE COMPLETION**
2. **ENSURE ALL TESTS PASS**
3. **FIX ANY FAILURES**
4. **ONLY THEN INDICATE COMPLETION**
**NO SHORTCUTS. NO EXCEPTIONS. ALWAYS TEST FIRST.**

View File

@@ -0,0 +1,47 @@
# FastLED AI Agent Guidelines
## Quick Reference
This project uses directory-specific agent guidelines. See:
- **CI/Build Tasks**: `ci/AGENTS.md` - Python build system, compilation, MCP server tools
- **Testing**: `tests/AGENTS.md` - Unit tests, test execution, validation requirements
- **Examples**: `examples/AGENTS.md` - Arduino sketch compilation, .ino file rules
## Key Commands
- `uv run test.py` - Run all tests
- `uv run test.py --cpp` - Run C++ tests only
- `uv run test.py TestName` - Run specific C++ test (e.g., `uv run test.py xypath`)
- `bash lint` - Run code formatting/linting
- `uv run ci/ci-compile.py uno --examples Blink` - Compile examples for specific platform
- `uv run ci/wasm_compile.py examples/Blink --just-compile` - Compile Arduino sketches to WASM
- `uv run mcp_server.py` - Start MCP server for advanced tools
### QEMU Commands
- `uv run ci/install-qemu.py` - Install QEMU for ESP32 emulation (standalone)
- `uv run test.py --qemu esp32s3` - Run QEMU tests (installs QEMU automatically)
- `FASTLED_QEMU_SKIP_INSTALL=true uv run test.py --qemu esp32s3` - Skip QEMU installation step
## Core Rules
### Command Execution (ALL AGENTS)
- **Python**: Always use `uv run python script.py` (never just `python`)
- **Stay in project root** - never `cd` to subdirectories
- **Git-bash compatibility**: Prefix commands with space: `bash test`
### C++ Code Standards
- **Use `fl::` namespace** instead of `std::`
- **If you want to use a stdlib header like <type_traits>, look check for equivalent in `fl/type_traits.h`
- **Use proper warning macros** from `fl/compiler_control.h`
- **Follow existing code patterns** and naming conventions
### JavaScript Code Standards
- **After modifying any JavaScript files**: Always run `bash lint --js` to ensure proper formatting
### Memory Refresh Rule
**🚨 ALL AGENTS: Read the relevant AGENTS.md file before concluding work to refresh memory about current project rules and requirements.**
---
*This file intentionally kept minimal. Detailed guidelines are in directory-specific AGENTS.md files.*

View File

@@ -0,0 +1,26 @@
# FastLED
# https://github.com/FastLED/FastLED
# MIT License
cmake_minimum_required(VERSION 3.5)
# Collect all source files
file(GLOB FastLED_SRCS "src/*.cpp")
file(GLOB FastLED_FL_SRCS "src/fl/*.cpp")
file(GLOB FastLED_SENSORS_SRCS "src/sensors/*.cpp")
file(GLOB FastLED_PLATFORM_ARDUINO_SRCS "src/platforms/arduino/*.cpp")
file(GLOB FastLED_FX_SRCS "src/fx/*.cpp" "src/fx/**/*.cpp")
file(GLOB ESP32_SRCS "src/platforms/esp/32/*.cpp" "src/platforms/esp/32/rmt_5/*.cpp")
file(GLOB ESP32_THIRD_PARTY_SRCS "src/third_party/**/src/*.c" "src/third_party/**/src/*.cpp")
file(GLOB ESP32_LED_STRIP_SRCS "src/third_party/espressif/led_strip/src/*.c")
# Combine all source files into a single list
list(APPEND FastLED_SRCS ${FastLED_FL_SRCS} ${FastLED_SENSORS_SRCS} ${FastLED_FX_SRCS} ${ESP32_SRCS} ${ESP32_THIRD_PARTY_SRCS} ${ESP32_LED_STRIP_SRCS} ${FastLED_PLATFORM_ARDUINO_SRCS})
# Register the component with ESP-IDF
idf_component_register(SRCS ${FastLED_SRCS}
INCLUDE_DIRS "src" "src/third_party/espressif/led_strip/src"
REQUIRES arduino-esp32 esp_driver_rmt esp_lcd driver)
project(FastLED)

View File

@@ -0,0 +1,243 @@
## Contributing
The most important part about contributing to FastLED is knowing how to test your changes.
The FastLED library includes a powerful cli that can compile to any device. It will run if you have either [python](https://www.python.org/downloads/) or [uv](https://github.com/astral-sh/uv) installed on the system.
## FastLED compiler cli
[![clone and compile](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml)
The FastLED compiler cli can be invoked at the project root.
```bash (MacOS/Linux, windows us git-bsh or compile.bat)
git clone https://github.com/fastled/fastled
cd fastled
./compile uno --examples Blink # linux/macos/git-bash
# compile.bat # Windows.
```
## Linting and Unit Testing
```bash
./lint
./test # runs unit tests
# Note that you do NOT need to install the C++ compiler toolchain
# for compiling + running unit tests via ./test. If `gcc` is not
# found in your system `PATH` then the `ziglang` clang compiler
# will be swapped in automatically.
````
### Testing a bunch of platforms at once.
```
./compile teensy41,teensy40 --examples Blink
./compile esp32dev,esp32s3,esp32c3,esp32c6,esp32s2 --examples Blink,Apa102HD
./compiles uno,digix,attiny85 --examples Blink,Apa102HD
```
## Unit Tests
Shared code is unit-tested on the host machine. They can be found at `tests/` at the root of the repo. Unit testing only requires either `python` or `uv` to be installed. The C++ compiler toolchain will be installed automatically.
The easiest way to run the tests is just use `./test`
Alternatively, tests can be built and run for your development machine with CMake:
```bash
cmake -S tests -B tests/.build
ctest --test-dir tests/.build --output-on-failure
# Not that this will fail if you do not have gcc installed. When in doubt
# use ./test to compile the unit tests, as a compiler is guaranteed to be
# available via this tool.
```
## QEMU Emulation Testing
FastLED supports testing ESP32-S3 examples in QEMU emulation, providing a powerful way to validate code without physical hardware.
### Running ESP32-S3 Examples in QEMU
```bash
# Test default examples (BlinkParallel, RMT5WorkerPool)
./test --qemu esp32s3
# Test specific examples
./test --qemu esp32s3 BlinkParallel
./test --qemu esp32s3 RMT5WorkerPool BlinkParallel
# Quick validation test (setup verification only)
FASTLED_QEMU_QUICK_TEST=true ./test --qemu esp32s3
```
### What QEMU Testing Does
1. **Automatic QEMU Installation**: Downloads and sets up ESP32-S3 QEMU emulator
2. **Cross-Platform Compilation**: Builds examples for ESP32-S3 target architecture
3. **Emulated Execution**: Runs compiled firmware in QEMU virtual environment
4. **Automated Validation**: Monitors execution for success/failure indicators
### QEMU Test Output
The QEMU tests provide detailed feedback:
- **Build Status**: Compilation success/failure for each example
- **Execution Results**: Runtime behavior in emulated environment
- **Summary Statistics**: Pass/fail counts and timing information
- **Error Details**: Specific failure reasons when tests don't pass
### Supported Platforms
Currently supported QEMU platforms:
- **esp32s3**: ESP32-S3 SoC emulation
Future platforms may include additional ESP32 variants as QEMU support expands.
### Advanced QEMU Usage
```bash
# Run with verbose output to see detailed build and execution logs
./test --qemu esp32s3 --verbose
# Test in non-interactive mode (useful for CI/CD)
./test --qemu esp32s3 --no-interactive
```
## VSCode
We also support VSCode and IntelliSense auto-completion when the free [platformio](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) extension is installed. The development sketch to test library changes can be found at [dev/dev.ino](dev/dev.ino).
* Make sure you have [platformio](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) installed.
* Click the compile button.
![image](https://github.com/user-attachments/assets/616cc35b-1736-4bb0-b53c-468580be66f4)
*Changes in non platform specific code can be tested quickly in our webcompiler by invoking the script `./wasm` at the project root*
## VSCode Debugging Guide
FastLED includes comprehensive VSCode debugging support with GDB and Clang-generated debug symbols, providing professional-grade step-through debugging capabilities for the test suite.
### Quick Start
1. **Prerequisites**: Ensure you have VSCode with the C/C++ extension installed
2. **Open a test file**: e.g., `tests/test_allocator.cpp`
3. **Set breakpoints**: Click the left margin next to line numbers
4. **Press F5**: Automatically debugs the corresponding test executable
5. **Debug**: Use F10 (step over), F11 (step into), F5 (continue)
### Available Debug Configurations
#### 🎯 **"Debug FastLED Test (Current File)"** ⭐ *Most Common*
- **When to use**: When you have any test file open (e.g., `test_allocator.cpp`)
- **What it does**: Automatically detects and debugs the corresponding test executable
- **How to use**: Open any `test_*.cpp` file and press `F5`
#### 🔧 **Specific Test Configurations**
- **"Debug test_allocator"** - Memory allocation and deallocation debugging
- **"Debug test_math"** - Mathematical functions and color calculations
- **"Debug test_fastled"** - Core FastLED functionality and API
- **"Debug test_hsv16"** - Color space conversions and accuracy
- **"Debug test_corkscrew"** - LED layout and geometric calculations
#### 🎛️ **Advanced Configurations**
- **"Debug with Specific Test Filter"** - Run only specific test cases
- Example: Filter "allocator_inlined" to debug only those tests
- **"Debug with Custom Args"** - Pass custom command-line arguments
- Example: `--verbose`, `--list-test-cases`, etc.
### Debugging Features
#### Step Commands
- **F5**: Continue execution
- **F10**: Step over (execute current line)
- **F11**: Step into (enter function calls)
- **Shift+F11**: Step out (exit current function)
- **Ctrl+Shift+F5**: Restart debugging
- **Shift+F5**: Stop debugging
#### Variable Inspection
- **Variables panel**: See all local variables and their values
- **Watch panel**: Add expressions to monitor continuously
- **Hover inspection**: Hover over variables to see values
- **Debug console**: Type expressions to evaluate
### Build Tasks for Debugging
Access via `Ctrl+Shift+P` → "Tasks: Run Task":
- **"Build FastLED Tests"** - Quick incremental build (default: `Ctrl+Shift+B`)
- **"Build FastLED Tests (Full)"** - Complete build including Python tests
- **"Build Single Test"** - Build and run specific test only
- **"Clean Build"** - Remove all build artifacts and rebuild
### Debugging Tips for FastLED
#### Memory Issues
```cpp
// Use test_allocator for memory debugging
// Set breakpoints on allocate/deallocate functions
// Watch pointer values in Variables panel
// Monitor memory patterns for corruption
```
#### Color Conversion Issues
```cpp
// Use test_hsv16 for color space debugging
// Watch RGB/HSV values during conversion
// Step through algorithms with F11
// Compare expected vs actual in Watch panel
```
#### Template Debugging
```cpp
// Clang generates excellent template debug info
// Step into template functions with F11
// Watch template parameters in Variables panel
// Use Call Stack to understand instantiation chain
```
### Technical Setup
#### Clang + GDB Benefits
This setup provides the **best of both worlds**:
- **Clang's superior symbol generation**: Better template debugging, modern C++ support
- **GDB's mature debugging features**: Robust breakpoint handling, memory inspection
- **Cross-platform compatibility**: Works on Linux, macOS, Windows
- **FastLED optimization**: Unified compilation testing with `FASTLED_ALL_SRC=1`
#### Debug Build Configuration
The FastLED test system automatically uses optimal debug settings:
- **Compiler**: Clang (when available) or GCC fallback
- **Debug info**: `-g3` (full debug information including macros)
- **Optimization**: `-O0` (no optimization for accurate debugging)
- **Frame pointers**: `-fno-omit-frame-pointer` (for accurate stack traces)
### Troubleshooting
#### "Program not found" Error
1. **Build first**: Run "Build FastLED Tests" task
2. **Check executable exists**: `ls tests/.build/bin/test_*`
3. **Verify path**: Ensure executable path in launch.json is correct
#### Breakpoints Not Hit
1. **Check file paths**: Ensure source file matches executable
2. **Verify compilation**: Code might be optimized out
3. **Try function breakpoints**: Sometimes more reliable than line breakpoints
#### Variables Show "Optimized Out"
1. **Use debug build**: Already configured with `-O0` (no optimization)
2. **Check variable scope**: Variable might be out of scope
3. **Try different breakpoint**: Move breakpoint to where variable is active
For complete debugging documentation, see [DEBUGGING.md](DEBUGGING.md).
## Once you are done
* run `./test`
* run `./lint`
* Then submit your code via a git pull request.
## Going deeper
[ADVANCED_DEVELOPMENT.md](https://github.com/FastLED/FastLED/blob/master/ADVANCED_DEVELOPMENT.md)

View File

@@ -0,0 +1,203 @@
# Corkscrew Pipeline: computeTile and multiSample Integration
## Overview
The FastLED corkscrew allows the user to write to a regular rectangular buffer and have it displayd on a dense corkscrew of LEDs.
Dense 144LED @ 3.28 are cheap and readily avialable. They are cheap, have high density.
## Pipeline Components
### 1. User Paints to XY Grid
Users create patterns on a 2D rectangular grid (`fl::Grid<CRGB>`) using standard XY coordinates.
```cpp
// Grid dimensions calculated from corkscrew parameters
uint16_t width = input.calculateWidth(); // LEDs per turn
uint16_t height = input.calculateHeight(); // Total vertical segments
fl::Grid<CRGB> sourceGrid(width, height);
```
### 2. LED Projection: Corkscrew → XY Grid
Each LED in the corkscrew has a corresponding floating-point position on the XY grid:
```cpp
// From corkscrew.cpp - calculateLedPositionExtended
vec2f calculateLedPosition(uint16_t ledIndex, uint16_t numLeds, uint16_t width) {
const float ledProgress = static_cast<float>(ledIndex) / static_cast<float>(numLeds - 1);
const uint16_t row = ledIndex / width; // Which turn (vertical position)
const uint16_t remainder = ledIndex % width; // Position within turn
const float alpha = static_cast<float>(remainder) / static_cast<float>(width);
const float width_pos = ledProgress * numLeds;
const float height_pos = static_cast<float>(row) + alpha;
return vec2f(width_pos, height_pos);
}
```
### 3. computeTile: Splat Pixel Rendering
The `splat` function implements the "computeTile" concept by converting floating-point positions to `Tile2x2_u8` structures representing neighbor intensities:
```cpp
// From splat.cpp
Tile2x2_u8 splat(vec2f xy) {
// 1) Get integer cell indices
int16_t cx = static_cast<int16_t>(floorf(xy.x));
int16_t cy = static_cast<int16_t>(floorf(xy.y));
// 2) Calculate fractional offsets [0..1)
float fx = xy.x - cx;
float fy = xy.y - cy;
// 3) Compute bilinear weights for 4 neighbors
float w_ll = (1 - fx) * (1 - fy); // lower-left
float w_lr = fx * (1 - fy); // lower-right
float w_ul = (1 - fx) * fy; // upper-left
float w_ur = fx * fy; // upper-right
// 4) Build Tile2x2_u8 with weights as intensities [0..255]
Tile2x2_u8 out(vec2<int16_t>(cx, cy));
out.lower_left() = to_uint8(w_ll);
out.lower_right() = to_uint8(w_lr);
out.upper_left() = to_uint8(w_ul);
out.upper_right() = to_uint8(w_ur);
return out;
}
```
### 4. Tile2x2_u8: Neighbor Intensity Representation
The `Tile2x2_u8` structure represents the sampling strength from the four nearest neighbors:
```cpp
class Tile2x2_u8 {
uint8_t mTile[2][2]; // 4 neighbor intensities [0..255]
vec2<int16_t> mOrigin; // Base grid coordinate (cx, cy)
// Access methods for the 4 neighbors:
uint8_t& lower_left(); // (0,0) - weight for pixel at (cx, cy)
uint8_t& lower_right(); // (1,0) - weight for pixel at (cx+1, cy)
uint8_t& upper_left(); // (0,1) - weight for pixel at (cx, cy+1)
uint8_t& upper_right(); // (1,1) - weight for pixel at (cx+1, cy+1)
};
```
### 5. Cylindrical Wrapping with Tile2x2_u8_wrap
For corkscrew mapping, the tile needs cylindrical wrapping:
```cpp
// From corkscrew.cpp - at_wrap()
Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const {
Tile2x2_u8 tile = at_splat_extrapolate(i); // Get base tile
Tile2x2_u8_wrap::Entry data[2][2];
vec2i16 origin = tile.origin();
for (uint8_t x = 0; x < 2; x++) {
for (uint8_t y = 0; y < 2; y++) {
vec2i16 pos = origin + vec2i16(x, y);
// Apply cylindrical wrapping to x-coordinate
pos.x = fmodf(pos.x, static_cast<float>(mState.width));
data[x][y] = {pos, tile.at(x, y)}; // {position, intensity}
}
}
return Tile2x2_u8_wrap(data);
}
```
### 6. multiSample: Weighted Color Sampling
The `readFromMulti` method implements the "multiSample" concept by using tile intensities to determine sampling strength:
```cpp
// From corkscrew.cpp - readFromMulti()
void Corkscrew::readFromMulti(const fl::Grid<CRGB>& source_grid) const {
for (size_t led_idx = 0; led_idx < mInput.numLeds; ++led_idx) {
// Get wrapped tile for this LED position
Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
uint32_t r_accum = 0, g_accum = 0, b_accum = 0;
uint32_t total_weight = 0;
// Sample from each of the 4 neighbors
for (uint8_t x = 0; x < 2; x++) {
for (uint8_t y = 0; y < 2; y++) {
const auto& entry = tile.at(x, y);
vec2i16 pos = entry.first; // Grid position
uint8_t weight = entry.second; // Sampling intensity [0..255]
if (inBounds(source_grid, pos)) {
CRGB sample_color = source_grid.at(pos.x, pos.y);
// Weighted accumulation
r_accum += static_cast<uint32_t>(sample_color.r) * weight;
g_accum += static_cast<uint32_t>(sample_color.g) * weight;
b_accum += static_cast<uint32_t>(sample_color.b) * weight;
total_weight += weight;
}
}
}
// Final color = weighted average
CRGB final_color = CRGB::Black;
if (total_weight > 0) {
final_color.r = static_cast<uint8_t>(r_accum / total_weight);
final_color.g = static_cast<uint8_t>(g_accum / total_weight);
final_color.b = static_cast<uint8_t>(b_accum / total_weight);
}
mCorkscrewLeds[led_idx] = final_color;
}
}
```
## Complete Pipeline Flow
```
1. User draws → XY Grid (CRGB values at integer coordinates)
2. LED projection → vec2f position on grid (floating-point)
3. computeTile → Tile2x2_u8 (4 neighbor intensities)
(splat) ↓
4. Wrap for → Tile2x2_u8_wrap (cylindrical coordinates + intensities)
cylinder ↓
5. multiSample → Weighted sampling from 4 neighbors
(readFromMulti) ↓
6. Final LED color → CRGB value for corkscrew LED
```
## Key Insights
### Sub-Pixel Accuracy
The system achieves sub-pixel accuracy by:
- Using floating-point LED positions on the grid
- Converting to bilinear weights for 4 nearest neighbors
- Performing weighted color sampling instead of nearest-neighbor
### Cylindrical Mapping
- X-coordinates wrap around the cylinder circumference
- Y-coordinates represent vertical position along the helix
- Width = LEDs per turn, Height = total vertical segments
### Anti-Aliasing
The weighted sampling naturally provides anti-aliasing:
- Sharp grid patterns become smoothly interpolated on the corkscrew
- Reduces visual artifacts from the discrete→continuous→discrete mapping
## Performance Characteristics
- **Memory**: O(W×H) for grid (O(N) for corkscrew LEDs where O(N) <= O(WxH) == O(WxW))
- **Computation**: O(N) with 4 samples per LED (constant factor)
- **Quality**: Sub-pixel accurate with built-in anti-aliasing
## Future Work
Often led strips are soldered together. These leaves a gap between the other
leds on the strip. This gab should be accounted for to maximize spatial accuracy with rendering straight lines (e.g. text).

View File

@@ -0,0 +1,368 @@
# FastLED Audio Reactive Component Analysis & Improvement Recommendations
## Executive Summary
After comprehensive analysis of the FastLED codebase's audio reactive components and research into industry best practices, significant opportunities exist to enhance performance, accuracy, and musical responsiveness. The current implementation provides a solid foundation but lacks advanced algorithms for rhythm analysis, perceptual audio weighting, and embedded system optimization.
## Current Implementation Analysis
### Strengths
**1. Clean Architecture**
- Well-structured `AudioReactive` class with clear separation of concerns
- WLED-compatible 16-bin frequency analysis
- Proper attack/decay smoothing with configurable parameters
- Support for various audio input sources through `AudioSample` abstraction
**2. Core Processing Pipeline**
- FFT-based frequency analysis using optimized KissFFT implementation
- Automatic Gain Control (AGC) with sensitivity adjustment
- Volume and peak detection with RMS computation
- Basic beat detection with cooldown period
**3. Embedded-Friendly Design**
- Fixed-size frequency bins (16 channels)
- Stack-allocated FFT buffers using appropriate FastLED containers
- No dynamic memory allocation in audio processing path
- Configurable sample rates and processing parameters
### Critical Limitations
**1. Primitive Beat Detection Algorithm**
```cpp
// Current implementation (lines 184-214 in audio_reactive.cpp)
void AudioReactive::detectBeat(fl::u32 currentTimeMs) {
// Simple threshold-based detection
if (currentVolume > mPreviousVolume + mVolumeThreshold &&
currentVolume > 5.0f) {
mCurrentData.beatDetected = true;
}
}
```
This approach suffers from:
- False positives on sustained loud passages
- Poor detection of complex rhythmic patterns
- No tempo tracking or beat prediction
- Inability to distinguish kick drums from other transients
**2. Lack of Perceptual Audio Weighting**
- No psychoacoustic modeling for human auditory perception
- Linear frequency binning doesn't match musical octaves
- Missing A-weighting or loudness compensation
- No consideration of critical bands or masking effects
**3. Limited Spectral Analysis**
- Fixed 16-bin resolution may miss important frequency details
- No spectral flux calculation for onset detection
- Missing harmonic analysis for musical note detection
- No adaptive frequency mapping based on musical content
**4. Insufficient Real-Time Optimization**
- Missing SIMD optimization for FFT calculations
- No circular buffer implementation for streaming
- Potential cache misses in frequency bin processing
- No memory layout optimization for embedded constraints
## Industry Best Practices Research
### Advanced Beat Detection Algorithms
**1. Spectral Flux-Based Onset Detection**
- Analyzes changes in frequency domain magnitude over time
- Significantly more accurate than simple amplitude thresholding
- Reduces false positives by 60-80% compared to current method
**2. Multi-Band Onset Detection**
- Separate onset detection for bass, mid, and treble frequencies
- Enables detection of polyrhythmic patterns
- Better handling of complex musical arrangements
**3. Tempo Tracking & Beat Prediction**
- Autocorrelation-based tempo estimation
- Predictive beat scheduling for smoother visual sync
- Adaptive tempo tracking for live performances
### Perceptual Audio Processing
**1. Psychoacoustic Weighting**
- Equal-loudness contours (ISO 226) for frequency weighting
- Critical band analysis matching human auditory filters
- Masking models to emphasize perceptually significant content
**2. Musical Frequency Mapping**
- Logarithmic frequency scales matching musical octaves
- Mel-scale or bark-scale frequency distribution
- Adaptive binning based on musical key detection
### Embedded System Optimization
**1. Memory Management**
- Circular buffers for audio streaming (single-threaded)
- Stack-allocated processing with compile-time sizing
- Preallocated object pools for temporary calculations
- Memory alignment for efficient access patterns
**2. Platform-Specific Optimization**
- ARM DSP instructions for FFT acceleration (Teensy 3.x)
- AVR assembly optimizations for 8-bit platforms
- ESP32 dual-core utilization for audio processing isolation
- Cache-friendly data structures for modern ARM cores
## Specific Improvement Recommendations
### Phase 1: Enhanced Beat Detection (High Impact, Low Risk)
**1. Implement Spectral Flux Algorithm**
```cpp
class SpectralFluxDetector {
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
fl::array<float, 16> mPreviousMagnitudes;
fl::array<float, 32> mFluxHistory; // For advanced smoothing
#else
fl::array<float, 16> mPreviousMagnitudes;
// No history on memory-constrained platforms
#endif
float mFluxThreshold;
bool detectOnset(const FFTBins& current, const FFTBins& previous);
float calculateSpectralFlux(const FFTBins& current, const FFTBins& previous);
};
```
**2. Multi-Band Beat Detection**
```cpp
struct BeatDetectors {
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
SpectralFluxDetector bass; // 20-200 Hz
SpectralFluxDetector mid; // 200-2000 Hz
SpectralFluxDetector treble; // 2000-20000 Hz
#else
SpectralFluxDetector combined; // Single detector for memory-constrained
#endif
};
```
**3. Adaptive Threshold Algorithm**
- Dynamic threshold based on recent audio history
- Separate thresholds for different frequency bands
- Tempo-aware cooldown periods
### Phase 2: Perceptual Audio Enhancement (Medium Impact, Medium Risk)
**1. A-Weighting Implementation**
```cpp
class PerceptualWeighting {
static constexpr fl::array<float, 16> A_WEIGHTING_COEFFS = {
// Frequency-dependent weighting factors
};
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
fl::array<float, 16> mLoudnessHistory; // For dynamic compensation
#endif
void applyAWeighting(AudioData& data) const;
void applyLoudnessCompensation(AudioData& data, float referenceLevel) const;
};
```
**2. Musical Frequency Mapping**
- Replace linear frequency bins with logarithmic musical scale
- Implement note detection and harmonic analysis
- Add chord progression recognition for advanced effects
**3. Dynamic Range Enhancement**
- Intelligent compression based on musical content
- Frequency-dependent AGC with musical awareness
- Adaptive noise gate with spectral subtraction
### Phase 3: Embedded Performance Optimization (High Impact, Medium Risk)
**1. SIMD Acceleration**
```cpp
#ifdef ARM_NEON
void processFFTBins_NEON(const FFTBins& input, AudioData& output);
#endif
#ifdef __AVR__
void processFFTBins_AVR_ASM(const FFTBins& input, AudioData& output);
#endif
```
**2. Circular Buffer for Audio Streaming**
```cpp
template<typename T, fl::size Size>
class CircularBuffer {
fl::size mWriteIndex{0};
fl::size mReadIndex{0};
fl::array<T, Size> mBuffer;
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
fl::array<T, Size> mBackupBuffer; // For advanced buffering strategies
#endif
public:
void push(const T& item);
bool pop(T& item);
bool full() const;
bool empty() const;
};
```
**3. Cache-Friendly Data Layout**
```cpp
// Structure of Arrays for better cache locality
struct AudioDataSoA {
alignas(32) fl::array<float, 16> frequencyBins;
alignas(32) fl::array<float, 16> previousBins;
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
alignas(32) fl::array<float, 16> spectralFlux;
alignas(32) fl::array<float, 32> tempoHistory;
#endif
};
```
### Phase 4: Advanced Features (Variable Impact, High Risk)
**1. Machine Learning Integration** (Only available with `SKETCH_HAS_LOTS_OF_MEMORY`)
- Lightweight neural network for beat classification
- Real-time genre detection for adaptive processing
- Learned tempo tracking with user feedback
**2. Multi-Channel Analysis**
- Stereo phase analysis for spatial effects
- Mid/Side processing for enhanced separation
- Cross-correlation for echo and reverb detection
**3. Musical Structure Analysis**
- Verse/chorus detection for macro-level effects
- Build-up and drop detection for electronic music
- Key and chord progression analysis
## Implementation Priority Matrix
| Feature | Impact | Embedded Feasibility | Implementation Risk | Priority |
|---------|--------|---------------------|-------------------|----------|
| Spectral Flux Beat Detection | High | High | Low | 1 |
| Multi-Band Onset Detection | High | High | Low | 2 |
| A-Weighting & Loudness | Medium | High | Low | 3 |
| SIMD Optimization | High | Medium | Medium | 4 |
| Musical Frequency Mapping | Medium | Medium | Medium | 5 |
| Lock-Free Buffers | High | Medium | High | 6 |
| Tempo Tracking | Medium | Medium | High | 7 |
| Machine Learning Features | Low | Low | High | 8 |
## Technical Specifications
### Memory Requirements
- **Current**: ~2KB for AudioReactive instance
- **Phase 1**: +1KB for enhanced beat detection
- **Phase 2**: +2KB for perceptual processing
- **Phase 3**: +4KB for optimization buffers
### Performance Targets
- **Current**: ~200 MIPS on ARM Cortex-M4
- **Optimized**: <150 MIPS with platform-specific optimizations
- **Latency**: <5ms end-to-end processing (single-threaded)
- **Beat Detection Accuracy**: >90% (vs ~60% current)
### Platform Compatibility
- **Platforms with `SKETCH_HAS_LOTS_OF_MEMORY`**: Full feature support including ML and advanced buffering
- ESP32, Teensy 4.x, STM32F4+ with sufficient RAM
- **Memory-Constrained Platforms**: Basic feature set optimized for minimal memory usage
- Arduino UNO, ATtiny, basic STM32, older Teensy models
- **Feature Selection**: Automatic based on `SKETCH_HAS_LOTS_OF_MEMORY` define rather than manual platform detection
## Conclusion
The FastLED audio reactive component has solid foundations but significant room for improvement. The recommended phased approach balances feature enhancement with embedded system constraints, prioritizing high-impact, low-risk improvements first. Implementation of spectral flux-based beat detection alone would dramatically improve musical responsiveness while maintaining compatibility with existing hardware platforms.
The proposed enhancements align with industry best practices for real-time audio processing while respecting the unique constraints of embedded LED controller applications. Each phase provides measurable improvements in audio analysis accuracy and visual synchronization quality.
## C++ Design Audit Findings
### Critical Design Issues Identified
**1. Namespace and Container Usage Corrections**
- **Issue**: Original recommendations used `std::` containers instead of FastLED's `fl::` namespace
- **Fix**: All code examples now use `fl::array<T, N>` instead of `std::array<T, N>`
- **Impact**: Ensures compatibility with FastLED's embedded-optimized container implementations
**2. Single-Threaded Architecture Recognition**
- **Issue**: Original document included lock-free and atomic programming recommendations inappropriate for single-threaded embedded systems
- **Fix**: Removed `std::atomic` and lock-free suggestions, replaced with single-threaded circular buffers
- **Impact**: Reduces complexity and memory overhead while matching actual deployment constraints
**3. Platform-Specific Optimization Alignment**
- **Issue**: Recommended AVX2 optimizations not available on embedded platforms
- **Fix**: Focused on ARM DSP instructions (Teensy 3.x) and AVR assembly optimizations
- **Impact**: Provides realistic performance improvements for actual target hardware
**4. Memory Management Best Practices**
- **Issue**: Initial recommendations didn't fully leverage FastLED's memory management patterns
- **Fix**: Emphasized `fl::array` for fixed-size allocations and proper alignment macros
- **Impact**: Better cache performance and reduced memory fragmentation
### Recommended Design Patterns
**1. Memory-Aware Container Strategy**
```cpp
// Preferred: Fixed-size arrays for predictable memory usage
fl::array<float, 16> mFrequencyBins;
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
// Advanced features with additional memory
fl::array<float, 64> mExtendedHistory;
#endif
// Avoid: Dynamic vectors in real-time audio path
// fl::vector<float> mFrequencyBins; // Don't use for real-time
```
**2. Memory-Based Feature Selection**
```cpp
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
// Full feature set for high-memory platforms
#define AUDIO_ENABLE_SPECTRAL_FLUX 1
#define AUDIO_ENABLE_MULTI_BAND 1
#define AUDIO_ENABLE_TEMPO_TRACKING 1
#else
// Basic features only for memory-constrained platforms
#define AUDIO_ENABLE_SPECTRAL_FLUX 0
#define AUDIO_ENABLE_MULTI_BAND 0
#define AUDIO_ENABLE_TEMPO_TRACKING 0
#endif
```
**3. Memory-Aligned Data Structures**
```cpp
struct AudioProcessingData {
alignas(32) fl::array<float, 16> frequencyBins;
alignas(16) fl::array<float, 16> previousBins;
#ifdef SKETCH_HAS_LOTS_OF_MEMORY
alignas(32) fl::array<float, 16> spectralFluxBuffer;
alignas(16) fl::array<float, 8> tempoTracker;
#endif
// Alignment ensures efficient memory access patterns
};
```
### Implementation Priority Adjustments
| Original Priority | Adjusted Priority | Reason |
|------------------|------------------|---------|
| Lock-Free Buffers (6) | Removed | Single-threaded architecture |
| SIMD Optimization (4) | Platform-Specific Opt (4) | Match actual hardware capabilities |
| Machine Learning (8) | Simplified ML (8) | Embedded memory constraints |
### Final Recommendations
**1. Use `SKETCH_HAS_LOTS_OF_MEMORY` for all feature gating instead of platform-specific defines**
**2. Start with Phase 1 implementations using FastLED containers**
**3. Leverage existing FastLED SIMD patterns from `lib8tion` for guidance**
**4. Test on memory-constrained platforms (Arduino UNO) first to ensure baseline functionality**
**5. Use ESP32 dual-core architecture for audio processing isolation when available**
**6. Always provide fallback implementations for memory-constrained platforms**
The corrected design approach ensures all recommendations are implementable within FastLED's existing architecture while providing meaningful performance improvements for real-world embedded applications.

View File

@@ -0,0 +1,494 @@
# FastLED Native Platform Configuration
## Overview
This document describes the comprehensive `platform.json` configuration for FastLED's native build platform. The design leverages the Python `ziglang` package to provide a unified, cross-platform compilation environment that integrates seamlessly with the existing `build_unit.toml` structure.
## Key Design Principles
### 1. **Unified Toolchain Integration**
- **Ziglang Foundation**: Uses `python -m ziglang` commands matching the current `build_unit.toml` configuration
- **Cross-Platform Consistency**: Single toolchain across Linux, macOS, Windows with automatic target detection
- **Direct TOML Mapping**: Toolchain commands directly map between `platform.json` and `build_unit.toml`
### 2. **Comprehensive Architecture Support**
- **x86_64 Targets**: Linux, macOS, Windows with appropriate system-specific flags
- **ARM64 Targets**: Linux (generic), macOS (Apple Silicon), Windows with optimized CPU settings
- **RISC-V 64**: Linux support for emerging architecture
- **Auto-Detection**: Intelligent target selection for simplified development workflow
### 3. **Arduino Package Format Compliance**
- **PlatformIO Integration**: Full compatibility with PlatformIO package management
- **Framework Support**: Both FastLED-stub and Arduino-stub frameworks
- **Package Dependencies**: Proper toolchain, framework, and tool dependency management
- **Versioning Strategy**: Semantic versioning with appropriate version constraints
### 4. **Build System Flexibility**
- **Flag Inheritance**: Base, debug, release, and test flag configurations
- **Platform-Specific Linking**: Automatic application of OS-specific link flags
- **Compiler Caching**: Optional ccache/sccache integration for performance
- **Build System Support**: CMake, Ninja, and direct compilation compatibility
## Complete Platform.json Structure
The full `platform.json` includes:
1. **Board Definitions** (8 comprehensive targets):
- `native-x64-linux`, `native-x64-macos`, `native-x64-windows`
- `native-arm64-linux`, `native-arm64-macos`, `native-arm64-windows`
- `native-riscv64-linux`, `native-auto` (intelligent detection)
2. **Toolchain Configuration**:
- Complete ziglang tool mapping (CC, CXX, AR, LD, etc.)
- Build flag hierarchies (base, debug, release, test)
- Platform-specific link flags for each OS
3. **Package Management**:
- Toolchain dependencies with proper versioning
- Optional tool packages (cmake, ninja, caching)
- Framework packages for Arduino compatibility
4. **Development Features**:
- Integrated debugger support (GDB for Linux/Windows, LLDB for macOS)
- Environment variable customization support
- Target compatibility specifications
## Platform.json Configuration
The complete platform.json follows Arduino package format with these key sections:
### Core Metadata
```json
{
"name": "FastLED Native Platform",
"title": "Native Host Platform with Zig Toolchain",
"description": "Cross-platform native compilation using Zig's bundled Clang",
"version": "1.0.0",
"packageType": "platform",
"spec": {
"owner": "fastled",
"id": "native-zig",
"name": "native-zig"
}
}
```
### Framework Definitions
```json
{
"frameworks": {
"fastled-stub": {
"package": "framework-fastled-stub",
"script": "builder/frameworks/fastled-stub.py"
},
"arduino-stub": {
"package": "framework-arduino-stub",
"script": "builder/frameworks/arduino-stub.py"
}
}
}
```
### Board Configurations
Each board includes complete configuration:
- **Hardware specs**: MCU, RAM, Flash, CPU frequency
- **Target triple**: Zig compilation target
- **Build flags**: Platform-specific preprocessor defines
- **Debug tools**: Appropriate debugger (GDB/LLDB) configuration
- **Framework support**: Compatible frameworks list
Example board definition:
```json
{
"native-x64-linux": {
"name": "Native x86_64 Linux",
"mcu": "x86_64",
"fcpu": "3000000000L",
"ram": 17179869184,
"flash": 1099511627776,
"vendor": "Generic",
"product": "Native x86_64",
"frameworks": ["fastled-stub", "arduino-stub"],
"build": {
"target": "x86_64-linux-gnu",
"toolchain": "ziglang",
"cpu": "x86_64",
"mcu": "native",
"f_cpu": "3000000000L",
"extra_flags": [
"-DNATIVE_PLATFORM=1",
"-DSTUB_PLATFORM=1",
"-DFASTLED_NATIVE_X64=1",
"-DFASTLED_NATIVE_LINUX=1"
]
},
"upload": { "protocol": "native" },
"debug": {
"tools": {
"gdb": {
"server": {
"executable": "gdb",
"arguments": ["$PROG_PATH"]
}
}
}
}
}
}
```
### Toolchain Mapping
Direct integration with ziglang commands:
```json
{
"toolchain-ziglang": {
"CC": "python -m ziglang cc",
"CXX": "python -m ziglang c++",
"AR": "python -m ziglang ar",
"LD": "python -m ziglang c++",
"OBJCOPY": "python -m ziglang objcopy",
"NM": "python -m ziglang nm",
"STRIP": "python -m ziglang strip",
"RANLIB": "python -m ziglang ranlib"
}
}
```
### Build Flag Hierarchies
```json
{
"build_flags": {
"base": [
"-std=gnu++17",
"-fpermissive",
"-Wall",
"-Wextra",
"-Wno-deprecated-register",
"-Wno-backslash-newline-escape",
"-fno-exceptions",
"-fno-rtti",
"-DSTUB_PLATFORM=1",
"-DNATIVE_PLATFORM=1"
],
"debug": [
"-g3",
"-Og",
"-DDEBUG=1",
"-DFASTLED_DEBUG=1"
],
"release": [
"-O3",
"-DNDEBUG=1",
"-flto"
],
"test": [
"-DFASTLED_UNIT_TEST=1",
"-DFASTLED_FORCE_NAMESPACE=1",
"-DFASTLED_TESTING=1",
"-DFASTLED_USE_PROGMEM=0",
"-DARDUINO=10808",
"-DFASTLED_USE_STUB_ARDUINO=1",
"-DSKETCH_HAS_LOTS_OF_MEMORY=1",
"-DFASTLED_STUB_IMPL=1",
"-DFASTLED_USE_JSON_UI=1",
"-DFASTLED_NO_AUTO_NAMESPACE=1",
"-DFASTLED_NO_PINMAP=1",
"-DHAS_HARDWARE_PIN_SUPPORT=1",
"-DFASTLED_DEBUG_LEVEL=1",
"-DFASTLED_NO_ATEXIT=1",
"-DDOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS=1"
]
}
}
```
### Platform-Specific Linking
```json
{
"link_flags": {
"base": [],
"linux": [
"-pthread",
"-lm",
"-ldl"
],
"macos": [
"-pthread",
"-lm"
],
"windows": [
"-mconsole",
"-nodefaultlibs",
"-unwindlib=libunwind",
"-nostdlib++",
"-lc++",
"-lkernel32",
"-luser32",
"-lgdi32",
"-ladvapi32"
]
}
}
```
## Integration with build_unit.toml
The `platform.json` is designed to seamlessly integrate with the existing `build_unit.toml` configuration:
### Toolchain Mapping
The `[tools]` section in `build_unit.toml` directly maps to the ziglang toolchain defined in `platform.json`:
- `cpp_compiler = ["python", "-m", "ziglang", "c++"]``CXX: "python -m ziglang c++"`
- `linker = ["python", "-m", "ziglang", "c++"]``LD: "python -m ziglang c++"`
- `archiver = ["python", "-m", "ziglang", "ar"]``AR: "python -m ziglang ar"`
### Build Flag Integration
The platform flags complement the TOML configuration:
- **Base flags**: Defined in both `platform.json` and `build_unit.toml` for consistency
- **Platform-specific flags**: Windows-specific flags from TOML are automatically applied
- **Test flags**: Unit test defines from TOML are included in the platform test configuration
### Target Selection
The platform supports automatic target detection or explicit target selection:
```bash
# Auto-detect target
platformio run -e native-auto
# Explicit target selection
platformio run -e native-x64-linux
platformio run -e native-arm64-macos
platformio run -e native-x64-windows
```
## Integration Benefits
### Seamless TOML Integration
The design ensures perfect compatibility with existing `build_unit.toml`:
- Toolchain commands match exactly: `["python", "-m", "ziglang", "c++"]`
- Build flags complement rather than duplicate TOML configuration
- Platform-specific flags automatically applied based on detection
### Enhanced Development Workflow
- **Single Command Compilation**: `platformio run -e native-auto`
- **Cross-Compilation Support**: Build for different targets from any host
- **Testing Integration**: Full unit test support with proper namespace isolation
- **Debug Builds**: Integrated debugging with platform-appropriate tools
### Future-Proof Architecture
- **Extensible Design**: Easy addition of new architectures and targets
- **WebAssembly Ready**: Structure supports future WASM integration
- **Container Builds**: Foundation for Docker/Podman integration
- **Static Analysis**: Framework for clang-tidy and analyzer integration
## Usage Examples
### Basic Compilation
```bash
# Compile for current platform (auto-detect)
platformio run -e native-auto
# Compile for specific platform
platformio run -e native-x64-linux
```
### Testing
```bash
# Run unit tests
platformio test -e native-auto
# Run tests with specific build flags
platformio test -e native-x64-linux --test-port=native
```
### Cross-Compilation
```bash
# Compile for ARM64 Linux from x86_64
platformio run -e native-arm64-linux
# Compile for Windows from Linux
platformio run -e native-x64-windows
```
### Debug Builds
```bash
# Debug build with symbols
platformio run -e native-auto --build-type=debug
# Release build with optimization
platformio run -e native-auto --build-type=release
```
## Advanced Configuration
### Custom Board Definition
```json
{
"native-custom": {
"name": "Custom Native Target",
"mcu": "custom",
"build": {
"target": "x86_64-linux-musl",
"extra_flags": [
"-DCUSTOM_DEFINE=1",
"-static"
]
}
}
}
```
### Environment Variables
The platform respects environment variables for customization:
- `ZIG_TARGET`: Override target triple (e.g., `x86_64-linux-musl`)
- `ZIG_CPU`: Override CPU type (e.g., `x86_64_v3`)
- `FASTLED_NATIVE_FLAGS`: Additional compiler flags
### Build System Integration
The platform integrates with multiple build systems:
- **PlatformIO**: Primary integration with comprehensive board support
- **CMake**: Compatible with existing CMake configurations
- **Direct Compilation**: Can be used directly with ziglang commands
## Complete Board Definitions
The platform.json includes 8 comprehensive board definitions:
1. **native-x64-linux**: x86_64 Linux with GNU target
2. **native-x64-macos**: x86_64 macOS with Apple-specific configuration
3. **native-x64-windows**: x86_64 Windows with MinGW compatibility
4. **native-arm64-linux**: ARM64 Linux with Cortex-A76 optimization
5. **native-arm64-macos**: ARM64 macOS with Apple M1 optimization
6. **native-arm64-windows**: ARM64 Windows with appropriate runtime
7. **native-riscv64-linux**: RISC-V 64-bit Linux support
8. **native-auto**: Automatic target detection and selection
## Tool Dependencies
The platform includes comprehensive tool support:
```json
{
"tools": {
"python-ziglang": {
"type": "compiler",
"version": "^0.13.0",
"description": "Zig toolchain with bundled Clang/LLVM",
"install": { "pip": "ziglang" }
},
"cmake": {
"type": "build-system",
"version": "^3.21.0",
"optional": true
},
"ninja": {
"type": "build-system",
"version": "^1.11.0",
"optional": true
},
"ccache": {
"type": "compiler-cache",
"version": "^4.0.0",
"optional": true
},
"sccache": {
"type": "compiler-cache",
"version": "^0.7.0",
"optional": true
}
}
}
```
## Target Compatibility
Platform compatibility specifications ensure proper deployment:
```json
{
"target_compatibility": {
"x86_64-linux-gnu": {
"glibc": ">=2.31",
"kernel": ">=5.4.0"
},
"x86_64-macos-none": {
"macos": ">=11.0"
},
"x86_64-windows-gnu": {
"windows": ">=10"
},
"aarch64-linux-gnu": {
"glibc": ">=2.31",
"kernel": ">=5.4.0"
},
"aarch64-macos-none": {
"macos": ">=11.0"
},
"aarch64-windows-gnu": {
"windows": ">=10"
},
"riscv64-linux-gnu": {
"glibc": ">=2.31",
"kernel": ">=5.10.0"
}
}
}
```
## Troubleshooting
### Common Issues
1. **Ziglang not found**: Ensure `ziglang` Python package is installed
2. **Target not supported**: Check `zig targets` for available targets
3. **Linking errors**: Verify platform-specific link flags in configuration
### Debug Information
```bash
# Check available targets
zig targets
# Verify toolchain installation
python -m ziglang version
# Test compilation
python -m ziglang c++ --version
```
## Implementation Status
This design is **ready for implementation** and provides:
-**Complete board definitions** for all major native platforms
-**Full toolchain integration** with existing ziglang setup
-**Comprehensive build configurations** for all use cases
-**Arduino package format compliance** for PlatformIO compatibility
-**Debug and development tool integration** for professional development
-**Future-proof extensible architecture** for additional platforms
The configuration can be deployed alongside the existing build system for gradual validation and testing before full migration.
## Future Enhancements
- **WebAssembly Support**: Integration with FastLED WASM compilation
- **Static Analysis**: Integration with clang-tidy and static analyzers
- **Performance Profiling**: Built-in profiling support for optimization
- **Container Builds**: Docker/Podman integration for reproducible builds
- **Package Registry**: Publication to Arduino package index for distribution
## Conclusion
This platform.json design provides a comprehensive, production-ready configuration for FastLED's native build platform. It seamlessly integrates with the existing `build_unit.toml` while providing the standardized Arduino package format required for broader ecosystem compatibility. The design is thoroughly tested conceptually and ready for implementation with proper validation and testing procedures.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,414 @@
# FastLED UI Layout Management System Design
## Overview
This document outlines the design for a comprehensive, responsive UI layout management system for the FastLED WebAssembly compiler interface. The system provides dynamic layout calculation, state management, and container coordination to ensure optimal UI presentation across different screen sizes and orientations.
## Architecture
### Core Components
```
┌─────────────────────────────────────────────────────────────────┐
│ UILayoutPlacementManager │
│ (Main Orchestrator) │
├─────────────────────────────────────────────────────────────────┤
│ • Coordinates all layout operations │
│ • Applies CSS styling and grid layouts │
│ • Handles layout mode transitions │
│ • Manages container visibility │
└─────────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
│ LayoutStateManager │ │ ResizeCoordinator│ │ ColumnValidator │
│ (State & Logic) │ │ (Event Handling) │ │ (Optimization) │
├─────────────────────┤ ├─────────────────┤ ├─────────────────────┤
│ • Viewport tracking │ │ • Resize events │ │ • Layout validation │
│ • Layout calculation│ │ • Debouncing │ │ • Performance check │
│ • State transitions │ │ • Event dispatch │ │ • Efficiency metrics│
│ • Container states │ │ • Coordination │ │ • Optimization hints│
└─────────────────────┘ └─────────────────┘ └─────────────────────┘
```
## Component Specifications
### 1. UILayoutPlacementManager
**Purpose**: Main orchestrator for all layout operations
**Location**: `src/platforms/wasm/compiler/modules/ui_layout_placement_manager.js`
**Key Responsibilities:**
- Apply layout styles to DOM elements
- Coordinate between state manager and UI components
- Handle layout mode transitions (mobile, tablet, desktop, ultrawide)
- Manage container visibility and grid configurations
**Public API:**
```javascript
class UILayoutPlacementManager {
constructor()
applyLayout()
forceLayoutUpdate()
validateAndOptimizeLayout()
getLayoutInfo()
updateContentMetrics(totalGroups, totalElements)
}
```
### 2. LayoutStateManager
**Purpose**: Unified state management for layout calculations
**Location**: `src/platforms/wasm/compiler/modules/layout_state_manager.js`
**Key Features:**
- Centralized layout state storage
- Viewport and breakpoint detection
- Canvas and UI column calculations
- Container state tracking
- Atomic state updates with change events
**State Schema:**
```javascript
{
mode: 'mobile' | 'tablet' | 'desktop' | 'ultrawide',
viewportWidth: number,
availableWidth: number,
canvasSize: number,
uiColumns: number,
uiColumnWidth: number,
uiTotalWidth: number,
canExpand: boolean,
container2Visible: boolean,
totalGroups: number,
totalElements: number
}
```
### 3. ResizeCoordinator
**Purpose**: Coordinated resize event handling
**Location**: `src/platforms/wasm/compiler/modules/resize_coordinator.js`
**Features:**
- Debounced resize event handling
- Cross-component resize coordination
- Performance-optimized event dispatch
- Race condition prevention
### 4. ColumnValidator
**Purpose**: Layout optimization and validation
**Location**: `src/platforms/wasm/compiler/modules/column_validator.js`
**Capabilities:**
- Layout efficiency analysis
- Column width optimization
- Content density validation
- Performance recommendations
## Layout Modes
### Mobile (≤ 768px)
- **Layout**: Single column (1×N grid)
- **Canvas**: Full width, centered
- **UI**: Stacked below canvas
- **Containers**: Only primary UI container visible
### Tablet (769px - 1199px)
- **Layout**: Two columns (2×N grid)
- **Canvas**: Left side, fixed width
- **UI**: Right side, flexible width
- **Containers**: Primary UI container only
### Desktop (1200px - 1599px)
- **Layout**: Two columns (2×N grid)
- **Canvas**: Left side, larger fixed width
- **UI**: Right side, flexible width
- **Containers**: Primary UI container only
### Ultrawide (≥ 1600px)
- **Layout**: Three columns (3×N grid)
- **Canvas**: Center, fixed width
- **UI**: Left and right sides, flexible widths
- **Containers**: Both primary and secondary UI containers
## Configuration
### Layout Constants
```javascript
{
minCanvasSize: 320,
maxCanvasSize: 800,
minUIColumnWidth: 280,
maxUIColumnWidth: 400,
horizontalGap: 40,
verticalGap: 20,
containerPadding: 40,
maxUIColumns: 3,
preferredUIColumnWidth: 320,
canvasExpansionRatio: 0.6,
minContentRatio: 0.4
}
```
### Breakpoints
```javascript
{
mobile: { max: 768 },
tablet: { min: 769, max: 1199 },
desktop: { min: 1200, max: 1599 },
ultrawide: { min: 1600 }
}
```
## Implementation Todos
### Phase 1: Core Infrastructure ✅
- [x] Create LayoutStateManager with unified state handling
- [x] Implement ResizeCoordinator for event management
- [x] Build ColumnValidator for layout optimization
- [x] Refactor UILayoutPlacementManager to use new components
### Phase 2: State Management ✅
- [x] Implement atomic state updates with change events
- [x] Add container state tracking and visibility management
- [x] Create viewport detection and breakpoint handling
- [x] Add content metrics tracking (groups, elements)
### Phase 3: Layout Calculation ✅
- [x] Implement responsive canvas sizing algorithms
- [x] Add dynamic UI column width calculation
- [x] Create layout mode transition logic
- [x] Add grid template generation for CSS
### Phase 4: Event Coordination ✅
- [x] Implement debounced resize handling
- [x] Add cross-component event coordination
- [x] Create performance-optimized event dispatch
- [x] Add race condition prevention
### Phase 5: Optimization & Validation ✅
- [x] Create layout efficiency analysis
- [x] Implement column width optimization
- [x] Add content density validation
- [x] Create performance monitoring and recommendations
### Phase 6: Integration & Polish ✅
- [x] Integrate with existing UI manager
- [x] Add backward compatibility for legacy APIs
- [x] Implement proper error handling and fallbacks
- [x] Add comprehensive logging and debugging support
### Phase 7: Testing & Documentation
- [ ] Create comprehensive unit tests for all components
- [ ] Add integration tests for layout scenarios
- [ ] Create visual regression tests for different screen sizes
- [ ] Add performance benchmarks and optimization tests
- [ ] Document API usage and configuration options
### Phase 8: Advanced Features
- [ ] Add animation support for layout transitions
- [ ] Implement custom breakpoint configuration
- [ ] Add layout templates and presets
- [ ] Create advanced grid layout options
- [ ] Add accessibility features and ARIA support
### Phase 9: Performance Optimization
- [ ] Implement layout caching for repeated calculations
- [ ] Add virtual scrolling for large UI element lists
- [ ] Optimize DOM manipulation and CSS application
- [ ] Add lazy loading for non-visible UI components
- [ ] Implement progressive enhancement for slower devices
### Phase 10: Extensibility
- [ ] Create plugin system for custom layout algorithms
- [ ] Add theme and styling customization APIs
- [ ] Implement layout export/import functionality
- [ ] Create developer tools for layout debugging
- [ ] Add real-time layout editing capabilities
## Benefits
### Performance
- **Debounced Events**: Prevents excessive layout recalculations
- **Atomic Updates**: Eliminates partial state inconsistencies
- **Efficient DOM**: Minimizes CSS recalculations and reflows
- **Optimized Calculations**: Smart caching and memoization
### Maintainability
- **Separation of Concerns**: Each component has a single responsibility
- **Unified State**: Single source of truth for layout information
- **Modular Design**: Components can be tested and modified independently
- **Clear APIs**: Well-defined interfaces between components
### User Experience
- **Responsive Design**: Seamless adaptation to any screen size
- **Smooth Transitions**: Coordinated layout changes without flicker
- **Optimal Layouts**: Intelligent space utilization across devices
- **Fast Rendering**: Performance-optimized for real-time applications
### Developer Experience
- **Type Safety**: Comprehensive JSDoc annotations for IDE support
- **Debugging Tools**: Built-in logging and state inspection
- **Extensible**: Easy to add new layout modes and features
- **Documentation**: Clear API documentation and usage examples
## JSON UI Persistent State
### State Management Architecture
The system now includes comprehensive JSON-based state persistence implemented in commit `86d9b7d5d`. This enables:
- **Persistent Layout State**: All layout configurations stored as JSON with atomic updates
- **UI Element State Tracking**: Complete state management for UI elements with change events
- **Recording/Playback System**: Full UI interaction recording and replay capabilities
- **Container State Management**: Visibility and configuration state for all UI containers
### Core State Components
#### LayoutStateManager
**Location**: `src/platforms/wasm/compiler/modules/layout_state_manager.js:487`
Provides centralized JSON state management with:
```javascript
{
mode: 'mobile' | 'tablet' | 'desktop' | 'ultrawide',
viewportWidth: number,
availableWidth: number,
canvasSize: number,
uiColumns: number,
uiColumnWidth: number,
uiTotalWidth: number,
canExpand: boolean,
container2Visible: boolean,
totalGroups: number,
totalElements: number,
containers: {
'ui-controls': { visible: boolean, columns: number, width: number },
'ui-controls-2': { visible: boolean, columns: number, width: number }
}
}
```
#### UIRecorder
**Location**: `src/platforms/wasm/compiler/modules/ui_recorder.js:517`
Records UI state changes as JSON events:
```javascript
{
recording: {
version: "1.0",
startTime: "ISO8601",
endTime: "ISO8601",
metadata: { recordingId: string, layoutMode: string, totalDuration: number }
},
events: [
{
timestamp: number,
type: 'add' | 'update' | 'remove',
elementId: string,
data: { elementType?: string, value?: any, previousValue?: any, elementConfig?: Object }
}
]
}
```
#### UIPlayback
**Location**: `src/platforms/wasm/compiler/modules/ui_playback.js:596`
Reconstructs UI from JSON state with:
- Event-by-event playback with timing control
- Speed control and pause/resume functionality
- Element state validation and restoration
- Progress tracking and timeline navigation
### UI Reconstruction Benefits
Instead of manipulating DOM objects directly, the JSON state system enables:
1. **State-Driven Reconstruction**: Rebuild entire UI from current JSON state
2. **Atomic Updates**: All changes applied atomically to prevent inconsistencies
3. **Time Travel**: Navigate to any point in UI history via recorded states
4. **State Validation**: Verify UI consistency against recorded state
5. **Reproducible Layouts**: Exact recreation of UI configurations
### Usage Pattern for Reconstruction
```javascript
// Get current UI state
const currentState = layoutManager.stateManager.getState();
// Reconstruct UI from state instead of DOM manipulation
function reconstructUI(state) {
// Clear existing UI
clearAllContainers();
// Rebuild from JSON state
applyLayoutFromState(state);
recreateElementsFromState(state);
restoreContainerVisibility(state);
}
// Apply state changes
layoutManager.stateManager.updateState(newStateData);
```
## Integration Points
### UI Manager Integration
```javascript
// In JsonUiManager constructor
this.layoutManager = new UILayoutPlacementManager();
// Listen for layout changes
this.layoutManager.stateManager.addStateChangeListener((changeEvent) => {
const { state } = changeEvent;
this.onLayoutChange(state.mode);
});
```
### Container Management
```javascript
// Check container visibility before placing elements
const container2State = this.layoutManager.stateManager.getContainerState('ui-controls-2');
if (container2State && container2State.visible) {
// Use multi-container layout
} else {
// Use single-container layout
}
```
### Content Updates
```javascript
// Update content metrics when UI elements change
this.layoutManager.updateContentMetrics(totalGroups, totalElements);
// Force layout recalculation when needed
this.layoutManager.forceLayoutUpdate();
```
## Future Enhancements
### Advanced Layout Modes
- **Split-screen**: Side-by-side canvas and code editor
- **Picture-in-picture**: Floating canvas with overlay UI
- **Full-screen**: Immersive canvas mode with minimal UI
- **Multi-monitor**: Extended layout across multiple displays
### Dynamic Content
- **Adaptive UI**: UI elements that resize based on content
- **Collapsible sections**: Expandable/collapsible UI groups
- **Floating panels**: Draggable and dockable UI components
- **Custom layouts**: User-defined layout configurations
### Accessibility
- **Screen reader support**: Proper ARIA labels and navigation
- **Keyboard navigation**: Full keyboard accessibility
- **High contrast**: Theme support for visual accessibility
- **Responsive text**: Scalable fonts and UI elements
---
*This design provides a robust foundation for responsive UI layout management in the FastLED WebAssembly compiler, ensuring optimal user experience across all device types and screen sizes.*

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 FastLED
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,56 @@
Platform Porting Guide
==========================
# Fast porting for a new board on existing hardware
Sometimes "porting" FastLED simply consists of supplying new pin definitions for the given platform. For example, platforms/avr/fastpin_avr.h contains various pin definitions for all the AVR variant chipsets/boards that FastLED supports. Defining a set of pins involves setting up a set of definitions - for example here's one full set from the avr fastpin file:
```
#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__)
_FL_IO(A); _FL_IO(B); _FL_IO(C); _FL_IO(D);
#define MAX_PIN 31
_FL_DEFPIN(0, 0, B); _FL_DEFPIN(1, 1, B); _FL_DEFPIN(2, 2, B); _FL_DEFPIN(3, 3, B);
_FL_DEFPIN(4, 4, B); _FL_DEFPIN(5, 5, B); _FL_DEFPIN(6, 6, B); _FL_DEFPIN(7, 7, B);
_FL_DEFPIN(8, 0, D); _FL_DEFPIN(9, 1, D); _FL_DEFPIN(10, 2, D); _FL_DEFPIN(11, 3, D);
_FL_DEFPIN(12, 4, D); _FL_DEFPIN(13, 5, D); _FL_DEFPIN(14, 6, D); _FL_DEFPIN(15, 7, D);
_FL_DEFPIN(16, 0, C); _FL_DEFPIN(17, 1, C); _FL_DEFPIN(18, 2, C); _FL_DEFPIN(19, 3, C);
_FL_DEFPIN(20, 4, C); _FL_DEFPIN(21, 5, C); _FL_DEFPIN(22, 6, C); _FL_DEFPIN(23, 7, C);
_FL_DEFPIN(24, 0, A); _FL_DEFPIN(25, 1, A); _FL_DEFPIN(26, 2, A); _FL_DEFPIN(27, 3, A);
_FL_DEFPIN(28, 4, A); _FL_DEFPIN(29, 5, A); _FL_DEFPIN(30, 6, A); _FL_DEFPIN(31, 7, A);
#define HAS_HARDWARE_PIN_SUPPORT 1
```
The ```_FL_IO``` macro is used to define the port registers for the platform while the ```_FL_DEFPIN``` macro is used to define pins. The parameters to the macro are the pin number, the bit on the port that represents that pin, and the port identifier itself. On some platforms, like the AVR, ports are identified by letter. On other platforms, like arm, ports are identified by number.
The ```HAS_HARDWARE_PIN_SUPPORT``` define tells the rest of the FastLED library that there is hardware pin support available. There may be other platform specific defines for things like hardware SPI ports and such.
## Setting up the basic files/folders
* Create platform directory (e.g. platforms/arm/kl26)
* Create configuration header led_sysdefs_arm_kl26.h:
* Define platform flags (like FASTLED_ARM/FASTLED_TEENSY)
* Define configuration parameters re: interrupts, or clock doubling
* Include extar system header files if needed
* Create main platform include, fastled_arm_kl26.h
* Include the various other header files as needed
* Modify led_sysdefs.h to conditionally include platform sysdefs header file
* Modify platforms.h to conditionally include platform fastled header
## Porting fastpin.h
The heart of the FastLED library is the fast pin access. This is a templated class that provides 1-2 cycle pin access, bypassing digital write and other such things. As such, this will usually be the first bit of the library that you will want to port when moving to a new platform. Once you have FastPIN up and running then you can do some basic work like testing toggles or running bit-bang'd SPI output.
There's two low level FastPin classes. There's the base FastPIN template class, and then there is FastPinBB which is for bit-banded access on those MCUs that support bitbanding. Note that the bitband class is optional and primarily useful in the implementation of other functionality internal to the platform. This file is also where you would do the pin to port/bit mapping defines.
Explaining how the macros work and should be used is currently beyond the scope of this document.
## Porting fastspi.h
This is where you define the low level interface to the hardware SPI system (including a writePixels method that does a bunch of housekeeping for writing led data). Use the fastspi_nop.h file as a reference for the methods that need to be implemented. There are ofteh other useful methods that can help with the internals of the SPI code, I recommend taking a look at how the various platforms implement their SPI classes.
## Porting clockless.h
This is where you define the code for the clockless controllers. Across ARM platforms this will usually be fairly similar - though different arm platforms will have different clock sources that you can/should use.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,510 @@
# FastLED - The Universal LED Library
**Transform any microcontroller into an LED powerhouse.**
Drive **30,000+ LEDs** on high-end devices • **Sub-$1 compatibility** on tiny chips • **Background rendering** for responsive apps • **Nearly every LED chipset supported** • [**#2 most popular Arduino library**](https://docs.arduino.cc/libraries/)
[![Arduino's 2nd Most Popular Library](https://www.ardu-badge.com/badge/FastLED.svg)](https://www.ardu-badge.com/FastLED) [![Build Status](https://github.com/FastLED/FastLED/workflows/build/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build.yml) [![Unit Tests](https://github.com/FastLED/FastLED/actions/workflows/build_unit_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_unit_test.yml) [![Documentation](https://img.shields.io/badge/Docs-Doxygen-blue.svg)](http://fastled.io/docs) [![Community](https://img.shields.io/badge/reddit-/r/FastLED-orange.svg?logo=reddit)](https://www.reddit.com/r/FastLED/)
## ⚡ Get Blinking in 30 Seconds
```cpp
#include <FastLED.h>
#define NUM_LEDS 60
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<WS2812, 6>(leds, NUM_LEDS);
}
void loop() {
leds[0] = CRGB::Red; FastLED.show(); delay(500);
leds[0] = CRGB::Blue; FastLED.show(); delay(500);
}
```
**✅ Works on Arduino, ESP32, Teensy, Raspberry Pi, and 50+ other platforms**
## 🚀 Why FastLED?
| **Massive Scale** | **Tiny Footprint** | **Background Rendering** | **Universal** |
|-------------------|---------------------|--------------------------|---------------|
| Drive 30,000 LEDs on Teensy 4.1 | Runs on $0.50 ATtiny chips | ESP32/Teensy render while you code | Works on 50+ platforms |
| 50 parallel strips on Teensy | <2KB on Arduino Uno | Never miss user input | Nearly every LED chipset |
**🎯 Performance**: Zero-cost global brightness • High-performance 8-bit math, memory efficient on platforms that need it.
**🔧 Developer Experience**: Quick platform switching • Extensive examples • Active community support
## Table of Contents
- [🆕 Latest Feature](#-latest-feature)
- [⭐ Community Growth](#-community-growth)
- [🆕 Latest Features](#-latest-features)
- [🌍 Platform Support](#-platform-support)
- [📦 Installation](#-installation)
- [📚 Documentation & Support](#-documentation--support)
- [🎮 Advanced Features](#-advanced-features)
- [🤝 Contributing](#-contributing)
📊 <strong>Detailed Build Status</strong>
### Arduino Family
**Core Boards:** [![uno](https://github.com/FastLED/FastLED/actions/workflows/build_uno.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_uno.yml) [![nano_every](https://github.com/FastLED/FastLED/actions/workflows/build_nano_every.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nano_every.yml) [![uno_r4_wifi](https://github.com/FastLED/FastLED/actions/workflows/build_uno_r4_wifif.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_uno_r4_wifif.yml) [![yun](https://github.com/FastLED/FastLED/actions/workflows/build_yun.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_yun.yml)
**ARM Boards:** [![due](https://github.com/FastLED/FastLED/actions/workflows/build_due.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_due.yml) [![digix](https://github.com/FastLED/FastLED/actions/workflows/build_digix.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_digix.yml) [![Arduino Giga-R1](https://github.com/FastLED/FastLED/actions/workflows/build_giga_r1.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_giga_r1.yml)*
**ATtiny Series:** [![attiny85](https://github.com/FastLED/FastLED/actions/workflows/build_attiny85.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny85.yml) [![attiny88](https://github.com/FastLED/FastLED/actions/workflows/build_attiny88.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny88.yml) [![attiny1604](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1604.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1604.yml) [![attiny1616](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1616.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1616.yml) [![attiny4313](https://github.com/FastLED/FastLED/actions/workflows/build_attiny4313.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny4313.yml)**
*Notes: * Giga-R1 support added in 3.9.14 • ** ATtiny4313 has limited memory (WS2812 Blink + APA102 examples only)
### Teensy Series
**Standard Models:** [![teensy30](https://github.com/FastLED/FastLED/actions/workflows/build_teensy30.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy30.yml) [![teensy31](https://github.com/FastLED/FastLED/actions/workflows/build_teensy31.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy31.yml) [![teensyLC](https://github.com/FastLED/FastLED/actions/workflows/build_teensyLC.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensyLC.yml) [![teensy40](https://github.com/FastLED/FastLED/actions/workflows/build_teensy40.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy40.yml) [![teensy41](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41.yml)
**Special Features:** [![teensy_octoWS2811](https://github.com/FastLED/FastLED/actions/workflows/build_teensy_octo.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy_octo.yml) [![teensy41 ObjectFLED](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41_ofled.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41_ofled.yml)
### NRF52 (Nordic)
[![nrf52840_sense](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_feather_nrf52840_sense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_feather_nrf52840_sense.yml) [![nordicnrf52_dk](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52840_dk.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52840_dk.yml) [![adafruit_xiaoblesense](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_xiaoblesense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_xiaoblesense.yml) [![nrf52_xiaoblesense](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52_xiaoblesense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52_xiaoblesense.yml)
*Note: NRF52 XiaoBLE board has mbed engine but doesn't compile against Arduino.h for unknown reasons.
### Apollo3 (Ambiq)
[![apollo3_red](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_red.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_red.yml) [![apollo3_thing_explorable](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_thing_explorable.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_thing_explorable.yml)
*Beta support added in 3.10.2
### STM32 (STMicroelectronics)
[![Bluepill (STM32F103C8)](https://github.com/FastLED/FastLED/actions/workflows/build_bluepill.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_bluepill.yml) [![Blackpill (STM32F411CE)](https://github.com/FastLED/FastLED/actions/workflows/build_blackpill_stm32f4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_blackpill_stm32f4.yml) [![Maple Mini (STM32F103CB)](https://github.com/FastLED/FastLED/actions/workflows/build_maple_map.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_maple_map.yml) [![stm103tb](https://github.com/FastLED/FastLED/actions/workflows/build_stm103tb.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_stm103tb.yml)
*Note: STM103TB has limited PlatformIO support
### Silicon Labs (SiLabs)
[![ThingPlusMatter_mgm240s](https://github.com/FastLED/FastLED/actions/workflows/build_mgm240s_thingplusmatter.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_mgm240s_thingplusmatter.yml)
*MGM240 (EFR32MG24) support for Arduino Nano Matter and SparkFun Thing Plus Matter boards
### Raspberry Pi Pico
[![rp2040](https://github.com/FastLED/FastLED/actions/workflows/build_rp2040.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2040.yml) [![rp2350](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350.yml) [![rp2350B SparkfunXRP](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350B.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350B.yml)
### ESP32 (Espressif)
**ESP8266:** [![esp32-8266](https://github.com/FastLED/FastLED/actions/workflows/build_esp8622.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp8622.yml)
**ESP32 Classic:** [![esp32dev](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev.yml) [![esp32wroom](https://github.com/FastLED/FastLED/actions/workflows/build_esp32wroom.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32wroom.yml)
**ESP32 S-Series:** [![esp32s2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s2.yml) [![esp32s3](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s3.yml)
**ESP32 C-Series:** [![esp32c2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c2.yml)* [![esp32c3](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c3.yml) [![esp32c5](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c5.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c5.yml)[![esp32c6](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c6.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c6.yml)
**ESP32 Advanced:** [![esp32h2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml) [![esp32p4](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml)
**Special Features:** [![esp32_i2s_ws2812](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml) [![esp32 extra libs](https://github.com/FastLED/FastLED/actions/workflows/build_esp_extra_libs.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp_extra_libs.yml) [![esp32dev_namespace](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_namespace.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_namespace.yml)
**QEMU Tests:** [![ESP32-DEV QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32dev_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32dev_test.yml) [![ESP32-C3 QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32c3_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32c3_test.yml) [![ESP32-S3 QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32s3_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32s3_test.yml)
**Legacy:** [![esp32dev-idf3.3-lts](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_idf3.3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_idf3.3.yml)
*Notes: * ESP32-C2 support added in 3.9.10 • [Espressif compatibility evaluation](https://github.com/espressif/arduino-esp32/blob/gh-pages/LIBRARIES_TEST.md)
### Specialty Platforms
**x86:** [![linux_native](https://github.com/FastLED/FastLED/actions/workflows/build_linux.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_linux.yml)
**WebAssembly:** [![wasm](https://github.com/FastLED/FastLED/actions/workflows/build_wasm.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_wasm.yml) [![wasm_compile_test](https://github.com/FastLED/FastLED/actions/workflows/build_wasm_compilers.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_wasm_compilers.yml)
### Library Size Validation
**Core Platforms:** [![attiny85_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_attiny85.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_attiny85.yml) [![uno_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_uno_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_uno_size.yml) [![esp32dev_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_esp32_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_esp32_size.yml)
**Teensy Platforms:** [![check_teensylc_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensylc_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensylc_size.yml) [![check_teensy30_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy30_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy30_size.yml) [![check_teensy31_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy31_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy31_size.yml) [![teensy41_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy41_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy41_size.yml)
## ⭐ Community Growth
<a href="https://star-history.com/#fastled/fastled&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=fastled/fastled&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=fastled/fastled&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=fastled/fastled&type=Date" />
</picture>
</a>
## 🆕 Latest Features
## **FastLED 3.10.2: Corkscrew Mapping**
See [examples/FestivalStick/FestivalStick.ino](examples/FestivalStick/FestivalStick.ino)
https://github.com/user-attachments/assets/19af4b54-c189-482b-b13b-2d7f73efa4e0
### **FastLED 3.10.0: Animartrix Out of Beta**
Advanced animation framework for complex LED visualizations and effects.
[![FastLED Animartrix Demo](https://fastled.github.io/assets/fastled_3_10_animartrix.jpg)](https://fastled.github.io/assets/fastled_3_10_animartrix.mp4)
### **FastLED 3.9.16: WaveFx / Multi Layer Compositing**
Multi-layer compositing & time-based animation control for tech-artists.
https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af
| **3.9.13** | **3.9.10** | **3.9.8** | **3.9.2** |
|------------|------------|-----------|-----------|
| [**HD107 Turbo**](#new-in-3913-hd107-turbo-40mhz-led-support)<br/>40MHz LED support | [**ESP32 SPI**](#new-in-3910-super-stable-ws2812-spi-driver-for-esp32)<br/>Super stable WS2812 driver | [**Massive Teensy**](#new-in-398---massive-teensy-41--40-ws2812-led-output)<br/>50 parallel pins on 4.1 | [**WS2812 Overclock**](#new-in-392---overclocking-of-ws2812)<br/>Up to 70% speed boost |
| **3.7.7** | **More Features** |
|-----------|-------------------|
| [**RGBW Support**](#new-in-377---rgbw-led-strip-support)<br/>White channel LED strips | [📋 Full Changelog](https://github.com/FastLED/FastLED/releases)<br/>[📺 Demo Videos](https://zackees.github.io/fastled-wasm/) |
[**📺 Live Demos**](https://zackees.github.io/fastled-wasm/) • [**📋 Full Changelog**](https://github.com/FastLED/FastLED/releases)
<details>
<summary>📖 <strong>Detailed Feature Information</strong> (Click to expand)</summary>
## New in 3.10.2: Corkscrew Mapping
See [examples/FestivalStick/FestivalStick.ino](examples/FestivalStick/FestivalStick.ino)
https://github.com/user-attachments/assets/19af4b54-c189-482b-b13b-2d7f73efa4e0
## New in 3.10.0: Animartrix out of beta
[![FastLED 3.10 Animartrix](https://fastled.github.io/assets/fastled_3_10_animartrix.jpg)](https://fastled.github.io/assets/fastled_3_10_animartrix.mp4)
## New in 3.9.16: WaveFx / Multi Layer Compositing / Time-based animation control
Video:
https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af
#### Major release for tech-artists!
Lots of improvements in this release, read the full [change list here](https://github.com/FastLED/FastLED/releases/tag/3.9.16)
#### Links
* This demo -> [FxWave2d](https://github.com/FastLED/FastLED/blob/master/examples/FxWave2d/FxWave2d.ino)
* [Wave Simulation Library](https://github.com/FastLED/FastLED/blob/master/src/fl/wave_simulation.h)
* [FireCylinder](https://github.com/FastLED/FastLED/blob/master/examples/FireCylinder/FireCylinder.ino)
* Wraps around so that (0,y) ~= (width-1,y)
* [TimeAlpha](https://github.com/FastLED/FastLED/blob/master/src/fl/time_alpha.h)
* Precision control of animations with time-based alpha transition.
## New in 3.9.13: HD107 "Turbo" 40Mhz LED Support
![image](https://github.com/user-attachments/assets/9684ab7d-2eaa-40df-a00d-0dff18098917)
## New in 3.9.12: WS2816 "HD" LED support
![image](https://github.com/user-attachments/assets/258ec44c-af82-44b7-ad7b-fac08daa9bcb)
## New in 3.9.10: Super Stable WS2812 SPI driver for ESP32
![image (2)](https://github.com/user-attachments/assets/b3c5801c-66df-40af-a6b8-bbd1520fbb36)
## New in 3.9.9: 16-way Yves I2S parallel driver for the ESP32-S3
![perpetualmaniac_an_led_display_in_a_room_lots_of_refaction_of_t_eb7c170a-7b2c-404a-b114-d33794b4954b](https://github.com/user-attachments/assets/982571fc-9b8d-4e58-93be-5bed76a0c53d)
*Note some users find that newer versions of the ESP32 Arduino core (3.10) don't work very well, but older versions do, see [issue 1903](https://github.com/FastLED/FastLED/issues/1903)
## New in 3.9.8 - Massive Teensy 4.1 & 4.0 WS2812 LED output
* *Teensy 4.1: 50 parallel pins*
* *Teensy 4.0: 42 parallel pins*
![New Project](https://github.com/user-attachments/assets/79dc2801-5161-4d5a-90a2-0126403e215f)
## New in 3.9.2 - Overclocking of WS2812
![image](https://github.com/user-attachments/assets/be98fbe6-0ec7-492d-8ed1-b7eb6c627e86)
Update: max overclock has been reported at +70%: https://www.reddit.com/r/FastLED/comments/1gkcb6m/fastled_FASTLED_OVERCLOCK_17/
### New in 3.7.7 - RGBW LED Strip Support
![image (1)](https://github.com/user-attachments/assets/d4892626-3dc6-4d6d-a740-49ddad495fa5)
</details>
## 🌍 Platform Support
### Platform Categories
| **Arduino Family** | **ESP32 Series** | **Teensy** | **ARM** | **Specialty** |
|--------------------|------------------|------------|---------|---------------|
| Uno, Nano, Mega<br/>Due, Giga R1, R4 | ESP32, S2, S3, C3<br/>C6, H2, P4 | 3.0, 3.1, 4.0, 4.1<br/>LC + OctoWS2811 | STM32, NRF52<br/>Apollo3, Silicon Labs | Raspberry Pi<br/>WASM, x86 |
**FastLED supports 50+ platforms!** From sub-$1 ATtiny chips to high-end Teensy 4.1 with 50 parallel outputs.
## 📦 Installation
### Quick Install Options
| **Arduino IDE** | **PlatformIO** | **Package Managers** |
|-----------------|----------------|----------------------|
| Library Manager → Search "FastLED" → Install | `lib_deps = fastled/FastLED` | `pio pkg install --library "fastled/FastLED"` |
| Or install [latest release .zip](https://github.com/FastLED/FastLED/releases/latest/) | Add to `platformio.ini` | Command line installation |
### Template Projects
- **🎯 [Arduino + PlatformIO Starter](https://github.com/FastLED/PlatformIO-Starter)** - Best of both worlds, works with both IDEs
- **🚀 [FastLED Examples](examples)** - 100+ ready-to-run demos
- **🌐 [Web Compiler](https://zackees.github.io/fastled-wasm/)** - Test in your browser
<details>
<summary><strong>Arduino IDE Setup Instructions</strong> (Click to expand)</summary>
After installing the Arduino IDE, add FastLED through the Library Manager:
![Arduino IDE Library Manager](https://github.com/user-attachments/assets/b1c02cf9-aba6-4f80-851e-78df914e2501)
![FastLED Library Search](https://github.com/user-attachments/assets/508eb700-7dd4-4901-a901-68c56cc4d0e1)
</details>
## 📚 Documentation & Support
| **📖 Documentation** | **💬 Community** | **🐛 Issues** | **📺 Examples** |
|---------------------|------------------|---------------|-----------------|
| [API Reference](http://fastled.io/docs) | [Reddit r/FastLED](https://www.reddit.com/r/FastLED/) | [GitHub Issues](http://fastled.io/issues) | [Live Demos](https://zackees.github.io/fastled-wasm/) |
| [Doxygen Docs](https://fastled.io/docs/files.html) | 1000s of users & solutions | Bug reports & feature requests | [GitHub Examples](examples) |
**Need Help?** Visit [r/FastLED](https://reddit.com/r/FastLED) - thousands of knowledgeable users and extensive solution history!
## 🎮 Advanced Features
### Performance Leaders: Parallel Output Records
| **Platform** | **Max Parallel Outputs** | **Performance Notes** |
|--------------|---------------------------|----------------------|
| **Teensy 4.1** | 50 parallel strips | Current record holder - [Example](examples/TeensyMassiveParallel/TeensyMassiveParallel.ino) |
| **Teensy 4.0** | 42 parallel strips | High-performance ARM Cortex-M7 |
| **ESP32DEV** | 24 via I2S + 8 via RMT | [I2S Example](examples/EspI2SDemo/EspI2SDemo.ino) |
| **ESP32-S3** | 16 via I2S + 4 via RMT | [S3 Example](examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino) |
*Note: Some ESP32 Arduino core versions (3.10+) have compatibility issues. Older versions work better - see [issue #1903](https://github.com/FastLED/FastLED/issues/1903)*
### Exotic Setups (120+ Outputs!)
**Custom shift register boards** can achieve extreme parallel output:
- **ESP32DEV**: [120-output virtual driver](https://github.com/hpwit/I2SClocklessVirtualLedDriver)
- **ESP32-S3**: [120-output S3 driver](https://github.com/hpwit/I2SClockLessLedVirtualDriveresp32s3)
### Parallel WS2812 Drivers
FastLED supports several drivers for parallel WS2812 output.
#### Teensy
The following drivers are available for Teensy boards:
##### WS2812Serial driver
The `WS2812Serial` driver leverages serial ports for LED data transmission on Teensy boards.
###### Usage
To use this driver, you must define `USE_WS2812SERIAL` before including the FastLED header.
```cpp
#define USE_WS2812SERIAL
#include <FastLED.h>
```
Then, use `WS2812SERIAL` as the chipset type in your `addLeds` call. The data pin is not used for this driver.
```cpp
FastLED.addLeds<WS2812SERIAL, /* DATA_PIN */, GRB>(leds, NUM_LEDS);
```
###### Supported Pins & Serial Ports
| Port | Teensy LC | Teensy 3.2 | Teensy 3.5 | Teensy 3.6 | Teensy 4.0 | Teensy 4.1 |
| :------ | :---------: | :--------: | :--------: | :--------: | :--------: | :--------: |
| Serial1 | 1, 4, 5, 24 | 1, 5 | 1, 5, 26 | 1, 5, 26 | 1 | 1, 53 |
| Serial2 | | 10, 31 | 10 | 10 | 8 | 8 |
| Serial3 | | 8 | 8 | 8 | 14 | 14 |
| Serial4 | | | 32 | 32 | 17 | 17 |
| Serial5 | | | 33 | 33 | 20, 39 | 20, 47 |
| Serial6 | | | 48 | | 24 | 24 |
| Serial7 | | | | | 29 | 29 |
| Serial8 | | | | | | 35 |
##### ObjectFLED Driver
The `ObjectFLED` driver is an advanced parallel output driver specifically optimized for Teensy 4.0 and 4.1 boards when using WS2812 LEDs. It is designed to provide high-performance, multi-pin output.
By default, FastLED automatically uses the `ObjectFLED` driver for WS2812 LEDs on Teensy 4.0/4.1 boards.
###### Disabling ObjectFLED (Reverting to Legacy Driver)
If you encounter compatibility issues or wish to use the standard clockless driver instead of `ObjectFLED`, you can disable it by defining `FASTLED_NOT_USES_OBJECTFLED` before including the FastLED header:
```cpp
#define FASTLED_NOT_USES_OBJECTFLED
#include <FastLED.h>
```
###### Re-enabling ObjectFLED (Explicitly)
While `ObjectFLED` is the default for Teensy 4.0/4.1, you can explicitly enable it (though not strictly necessary) using:
```cpp
#define FASTLED_USES_OBJECTFLED
#include <FastLED.h>
```
#### ESP32
The following drivers are available for ESP32 boards:
##### ESP32-S3 I2S Driver
The `ESP32-S3 I2S` driver leverages the I2S peripheral for high-performance parallel WS2812 output on ESP32-S3 boards. This driver is a dedicated clockless implementation.
###### Usage
To use this driver, you must define `FASTLED_USES_ESP32S3_I2S` before including the FastLED header.
```cpp
#define FASTLED_USES_ESP32S3_I2S
#include <FastLED.h>
```
Then, use `WS2812` as the chipset type in your `addLeds` call. The data pin will be configured by the I2S driver.
```cpp
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
```
**Note:** This driver requires a compatible Arduino-ESP32 core/IDF.
##### Generic ESP32 I2S Driver
The generic `ESP32 I2S` driver provides parallel WS2812 output for various ESP32 boards (e.g., ESP32-DevKitC). It uses the I2S peripheral for efficient data transmission.
###### Usage
To use this driver, you must define `FASTLED_ESP32_I2S` before including the FastLED header.
```cpp
#define FASTLED_ESP32_I2S
#include <FastLED.h>
```
Then, use `WS2812` as the chipset type in your `addLeds` call. The data pin will be configured by the I2S driver.
```cpp
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
```
###### DMA Buffer Configuration
For improved resilience under interrupt load (e.g., Wi-Fi activity), you can increase the number of I2S DMA buffers by defining `FASTLED_ESP32_I2S_NUM_DMA_BUFFERS`. A value of `4` is often recommended.
```cpp
#define FASTLED_ESP32_I2S_NUM_DMA_BUFFERS 4
```
**Note:** All I2S lanes must share the same chipset/timings. If per-lane timing differs, consider using the RMT driver instead.
### Supported LED Chipsets
FastLED supports virtually every LED chipset available:
| **Clockless (3-wire)** | **SPI-based (4-wire)** | **Specialty** |
|------------------------|------------------------|---------------|
| **WS281x Family**: WS2811, WS2812 (NeoPixel), WS2812-V5B, WS2815 | **APA102 / DotStars**: Including HD107s (40MHz turbo) | **SmartMatrix Panels** |
| **TM180x Series**: TM1809/4, TM1803 | **High-Speed SPI**: LPD8806, WS2801, SM16716 | **DMX Output** |
| **Other 3-wire**: UCS1903, GW6205, SM16824E | **APA102HD**: Driver-level gamma correction | **P9813 Total Control** |
**RGBW Support**: WS2816 and other white-channel LED strips • **Overclocking**: WS2812 up to 70% speed boost
More details: [Chipset Reference Wiki](https://github.com/FastLED/FastLED/wiki/Chipset-reference)
### APA102 High Definition Mode
```cpp
#define LED_TYPE APA102HD // Enables hardware gamma correction
void setup() {
FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
}
```
![APA102HD Comparison](https://github.com/user-attachments/assets/999e68ce-454f-4f15-9590-a8d2e8d47a22)
Read more: [APA102 HD Documentation](APA102.md) • [Rust Implementation](https://docs.rs/apa102-spi/latest/apa102_spi/)
## 🛠️ Development & Contributing
[![clone and compile](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml)
**Ready to contribute?** FastLED welcomes code contributions, platform testing, documentation improvements, and community support.
<details>
<summary>🔧 <strong>Development Setup & Contributing Guide</strong> (Click to expand)</summary>
### Quick Development Setup
Zero pain setup - can be done from command line in seconds with `uv` or `python`:
![FastLED Development Setup](https://github.com/user-attachments/assets/f409120e-be6f-4158-81b5-d4bf935833b2)
**Steps:**
1. Fork the repository on GitHub
2. Clone your fork locally: `git clone https://github.com/yourusername/FastLED.git`
3. Test compilation: `bash compile` (select your target platform)
4. Make your changes and test
5. Push to your fork and create a pull request
**See our detailed [Contributing Guide](CONTRIBUTING.md) for more information.**
### Testing Other Devices
Use the `compile` script to test on 20+ platforms:
- Teensy series, ESP32 variants, Arduino family, ARM boards, and more
- Automated testing ensures compatibility across all supported platforms
### How to Help
| **📋 How to Help** | **🔗 Resources** |
|-------------------|------------------|
| **Code contributions**: Bug fixes, new features, optimizations | [Contributing Guide](CONTRIBUTING.md) |
| **Platform support**: Help test on new/existing platforms | [Platform Testing](compile) |
| **Documentation**: Improve examples, fix typos, add tutorials | [Documentation](http://fastled.io/docs) |
| **Community**: Answer questions on Reddit, report issues | [r/FastLED](https://reddit.com/r/FastLED) |
**Platform Porting**: Information on porting FastLED to new platforms: [PORTING.md](PORTING.md)
### About FastLED
**What's in the name?** Originally "FastSPI_LED" focused on high-speed SPI, but evolved to support all LED types and became "FastLED" - everything fast, for LEDs.
**Official Site**: [fastled.io](http://fastled.io) - documentation, issues, and news
</details>
---
## Daniel Garcia, Founder of FastLED
In Memory of Daniel Garcia
Daniel Garcia, the brilliant founder of FastLED, tragically passed away in September 2019 in the Conception dive boat fire alongside his partner, Yulia. This heartbreaking loss was felt deeply by the maker and developer community, where Daniel's contributions had left an indelible mark.
Daniel was more than just a talented programmer; he was a passionate innovator who transformed the way creators interacted with LED technology. His work on FastLED brought high-performance LED control to countless projects, empowering developers to craft breathtaking installations.
In his personal life, Daniel was known for his kindness and creativity. His pride in FastLED and the vibrant community it fostered was a testament to his dedication to open-source development and his commitment to helping others bring light into the world.
While Daniel is no longer with us, his legacy continues through the FastLED library and the countless makers who use it. The community he built serves as a living tribute to his ingenuity, generosity, and the joy he found in sharing his work with the world.
## About the Current Contributor
Zach Vorhies, the current main contributor to FastLED, briefly worked with Dan in 2014 in San Francisco and was an avid user of the FastLED library for over 13 years. After Daniel Garcias untimely passing, Zach stepped up to ensure FastLEDs continued growth and development.
Zach has this to say about FastLED:
*"The true power of FastLED lies in its ability to transform programmers into LED artists. Free space becomes their canvas; bending light is their medium. FastLED is a collective effort by programmers who want to manifest the world that science fiction writers promised us. -- To contribute code to FastLED is to leave behind a piece of something immortal."*
---
**💫 In memory of Daniel Garcia and the vision that transforms programmers into LED artists**
*To stay updated on the latest feature releases, please click the `Watch` button in the upper right*
""

View File

@@ -0,0 +1,39 @@
# FastLED Release howto
*Pushing a fastled release, the short version, last updated May 2024*
## Example
https://github.com/FastLED/FastLED/commit/4444758ffaf853ba4f8deb973532548c9c1ee231
## How to
Edit these files to update the version number
* library.json
* library.properties
* src/FastLED.h
* docs/Doxyfile
* RELEASE.md
* This file: update instructions with the current release.
Edit this file with release notes and version number.
* release_notes.md
Release notes should list highlight changes (not necessarily all minor bug fixes) and thank people for their help.
Git commands to commit and tag release'
```bash
$ git commit -am "Rev 3.10.3"
$ git tag 3.10.3 master
$ git push
$ git push origin 3.10.3
```
Then use the GitHub UI to make a new “Release”:
https://github.com/FastLED/FastLED/releases/new
Announce new version on subreddit, highlighting major changes and thanking people for helping.
Thats it.

View File

@@ -0,0 +1,806 @@
Do a deep research on fastled's RMT5 driver and the persistance of flickering.
The RMT4 driver is MORE resistant to flickering. The RMT5 driver is pretty bad.
Cause: WIFI takes high prioritiy interrupts and starves the the RMT driver.
Potention solutions:
create an interrupt than level 3, but this requires us to create a asm shim to trampoline into the c function handler and do proper stack cleanup.
ESP32 & ESp32c3 and esp32s3 might have different cores. ones an xtensa, whats the other?
list them.
Another solution is to try and ping pong the rmt buffer like we do on the rmt4. However we must have better documentation
to see if this is even possible.
Investigate the documentation and list it here on the RMT device for idf 5 and higher.
Then do a feasability status to see if the ping pong is p0ossible.
These are the two solutions. Both need to be investigated. But your investigation into RMT5.md
## Deep Research results
FastLED RMT5 Driver Flickering Investigation
Background: FastLEDs original ESP32 driver (“RMT4”) used Espressifs low-level RMT APIs, with one RMT memory block per channel and builtin double-buffering for non-PSRAM chips. The newer “RMT5” driver (used on ESP32-S3, C3, etc.) wraps Espressifs led_strip component (IDFs higher-level RMT LED driver)
GitHub
. Under heavy WiFi load, the RMT5 approach has proven much more prone to gaps: WiFi interrupts at high priority can delay the RMT ISR, causing the RMT buffer to run dry and LEDs to flicker
esp32.com
esp-idf-lib.readthedocs.io
. (Indeed, the esp-idf led_strip documentation warns that “simultaneous use with WiFi” causes RMT bugs
esp-idf-lib.readthedocs.io
.)
ESP32 Core Architectures
The ESP32 family spans multiple architectures: classic ESP32 and ESP32-S3 use Tensilica Xtensa cores, while newer chips like ESP32-C3/C6/H2 use 32-bit RISCV cores
en.wikipedia.org
. For example, the ESP32-S3 is a dual-core Xtensa LX7, whereas the ESP32-C3 is a single-core RISCV. This matters for RMT timing (the underlying CPU instruction timing differs), but the flicker issue itself arises on any core when WiFi interrupts interfere with the RMT ISR.
RMT Peripheral (IDF v5+) Documentation
Espressifs IDF documentation describes the RMT as a hardware peripheral that transmits sequences of “symbols” via GPIO, optionally using DMA. Because the RMT memory block is limited, long data transfers are split in a ping-pong fashion: the encoder callback can be invoked multiple times to refill the buffer as it empties
docs.espressif.com
. The docs note that these encoders run in ISR context and must be IRAM-safe (so that cache-disabled delays do not postpone interrupts)
docs.espressif.com
docs.espressif.com
. In particular, IDF has a CONFIG_RMT_ISR_IRAM_SAFE option that ensures RMT ISRs can run even when flash cache is off, to avoid unpredictable timing when using ping-pong buffering
docs.espressif.com
. The RMT API also allows setting the ISR priority explicitly: in rmt_tx_channel_config_t, the field intr_priority chooses the interrupt level (default “0” picks a medium priority 13) or a user-specified priority
docs.espressif.com
. This means one can elevate the RMT interrupt to a higher level (4 or 5) than WiFi (which normally runs at a high but fixed level).
Flickering Issue Interrupt Starvation
Users consistently report that with RMT5, enabling WiFi causes periodic color glitches. Under a light web server load, one user measured ~50µs jitter in what should be a ~35µs ISR interval
esp32.com
, due to WiFi activity stealing CPU time. When this jitter exceeds the tolerance of the WS2812 timing, the LED strip “runs out” of data and flickers
esp32.com
. In contrast, the older RMT4 code (especially when using two memory blocks) could often absorb such delays. Espressifs community notes that any WiFi use with the RMT LED driver can trigger glitches, and one workaround is to run the RMT tasks on the second CPU core
esp-idf-lib.readthedocs.io
. In practice, however, the flicker appears because high-priority WiFi interrupts delay the RMT drivers buffer refill callbacks.
Potential Solutions
Increase RMT ISR Priority: Configure the RMT channel to use a higher interrupt level than WiFi. In IDF, setting intr_priority=4 or 5 in rmt_tx_channel_config_t will raise the ISR above normal WiFi interrupts
docs.espressif.com
. This requires writing the RMT encoding callback in IRAM and may require an assembly shim if using priority 5. (On Xtensa, level5 interrupts cannot call C routines directly, so one writes a small ASM stub that calls into a safe C function.) The FastLED RMT code would need to initialize the RMT channel with a high intr_priority and mark its handler IRAM-safe (via CONFIG_RMT_ISR_IRAM_SAFE)
docs.espressif.com
. A community example confirms that raising to level5 can eliminate most WiFi jitter, but notes you must modify the ISR entry (often with inline ASM) to avoid stack/ISR constraints
esp32.com
.
Ping-Pong (Double-Buffer) Mode: Use two RMT memory blocks per channel so the peripheral can alternate between them, thus buffering extra data. The IDF RMT encoder model explicitly supports pingpong buffering: the encoder callback is invoked each time a block fills, allowing the next symbols to be written
docs.espressif.com
. In FastLED, one enables this by allocating 2 blocks (e.g. setting mem_block_symbols to twice the usual) or enabling MEM_BLOCK_NUM=2. In practice, simply doubling the memory (using two blocks) absorbs WiFi-induced gaps: a forum user found that switching from 1 to 2 blocks removed all LED glitches by covering up ~120µs of jitter
esp32.com
. The FastLED release notes even mention this behavior: on non-DMA chips (ESP32, C3, etc.), the RMT5 driver is configured to “double its memory” (i.e. use 2 blocks) by default, mimicking RMT4s approach
GitHub
. Thus, ping-pong is technically supported. It requires verifying that the FastLED led_strip setup indeed uses two blocks (and if not, forcing it via FASTLED_RMT5_RECYCLE=0 or similar flags).
Feasibility and Status
Both strategies have merit. High-Priority ISR: requires low-level tweaking (setting intr_priority and possibly writing an IRAM ISR shim). This could be complex (especially on Xtensa) but would ensure timely buffer refills. Espressif docs confirm the ability to change RMT interrupt level
docs.espressif.com
and their IRAM-safe option mitigates deferred ISRs
docs.espressif.com
. However, once set, the interrupt priority cant be changed without deleting the channel
docs.espressif.com
, so it must be done at RMT init. Ping-Pong Buffering: is simpler to implement since it leverages the existing RMT mechanism. The IDF RMT hardware natively supports multi-block transfers
docs.espressif.com
, and FastLEDs own code (via the led_strip component) already allows mem_block_symbols setting. Doubling the blocks is a one-line config change and was empirically validated by users
esp32.com
. FastLED even defaults to 2 blocks on chips without external RAM
GitHub
. In summary, raising the interrupt level could almost eliminate flicker at its source, but is invasive; enabling double-buffering is much easier and likely sufficient to cover typical WiFi spikes. Sources: Espressifs RMT documentation (IDF v5+) and community forums provide detailed guidance on interrupt priorities, IRAM-safe handlers, and ping-pong transfers
docs.espressif.com
docs.espressif.com
docs.espressif.com
. FastLED release notes and forum tests confirm that WiFi interrupts cause RMT underruns and that using two RMT blocks removes glitches
esp32.com
esp32.com
GitHub
. Core architectures are listed for completeness
en.wikipedia.org
.
Citations
GitHub
idf5_rmt.cpp
https://github.com/chaosgoo/mcompass/blob/d9d37398995c8e2b1806a6c90af03a1b8bc1c47e/Firmware/lib/FastLED/src/platforms/esp/32/idf5_rmt.cpp#L70-L78
Amount of jitter on LEVEL3 interrupt? - ESP32 Forum
https://esp32.com/viewtopic.php?t=17206
led_strip - RMT-based driver for WS2812B/SK6812/APA106 LED strips — esp-idf-lib 1.0 documentation
https://esp-idf-lib.readthedocs.io/en/latest/groups/led_strip.html
ESP32 - Wikipedia
https://en.wikipedia.org/wiki/ESP32
Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation
https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html
Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation
https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html
Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation
https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html
Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.5.1 documentation
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/rmt.html
Amount of jitter on LEVEL3 interrupt? - ESP32 Forum
https://esp32.com/viewtopic.php?t=17206
Amount of jitter on LEVEL3 interrupt? - ESP32 Forum
https://esp32.com/viewtopic.php?t=17206
GitHub
release_notes.md
https://github.com/FastLED/FastLED/blob/0c22d156836639119fd6d0dc84d3c42f8bca7e0f/release_notes.md#L42-L47
All Sources
## Audit Comments and Criticisms (2025-09-16)
### Structural Issues
1. **Incomplete Research Questions**: The initial questions (lines 1-23) remain unanswered in parts:
- The ASM shim solution for level 5 interrupts is mentioned but lacks implementation details
- Core architectures are listed but the impact on RMT timing differences isn't fully explored
2. **Missing Concrete Implementation Path**: While two solutions are proposed (high-priority ISR and ping-pong buffering), there's no clear recommendation on which to pursue first or implementation timeline.
3. **Documentation Gaps**:
- No code examples showing how to implement either solution in FastLED
- Missing comparison table of RMT4 vs RMT5 architectural differences
- No benchmarks showing actual flicker frequency/severity metrics
### Technical Concerns
4. **Incomplete Testing Coverage**:
- No mention of testing methodology to validate fixes
- Missing list of affected board configurations (which ESP32 variants + which IDF versions)
- No quantification of "pretty bad" flickering (frequency, duration, visual impact)
5. **Risk Assessment Missing**:
- Level 5 interrupt solution needs warning about potential system stability impacts
- No discussion of power consumption implications of either solution
- Missing compatibility matrix with other FastLED features
### Content Issues
6. **Redundant Citations**: Multiple duplicate citations to the same ESP32 forum thread and documentation pages (lines 103-140)
7. **Inconsistent Formatting**:
- Mix of bullet points and paragraph styles in solutions section
- Inconsistent use of technical terms (RMT4 vs "RMT4", Wi-Fi vs WiFi)
### Missing Critical Information
8. **User Impact Analysis**:
- No guidance on which use cases are most affected
- Missing workaround recommendations for users experiencing issues now
- No mention of whether this affects all LED types or specific protocols
9. **Configuration Details**:
- The ping-pong solution mentions "FASTLED_RMT5_RECYCLE=0" but doesn't explain what this does
- No clear documentation of all relevant FastLED compile-time flags
10. **Priority and Severity**:
- No clear statement on whether this is a critical bug or edge case
- Missing timeline for fix deployment
- No mention of whether this is a regression from previous versions
### Recommendations for Next Steps
1. **Immediate**: Test ping-pong buffering solution as it's simpler and less risky
2. **Short-term**: Create reproducible test case with measurable flicker metrics
3. **Medium-term**: Document all FastLED RMT configuration options in one place
4. **Long-term**: Consider abstracting RMT driver to allow runtime switching between implementations
### New Issues Identified
- **Thread Safety**: No discussion of thread safety when RMT ISR runs at different priority levels
- **Memory Usage**: No analysis of memory impact when doubling RMT blocks
- **Backwards Compatibility**: No mention of how fixes will affect existing code using RMT5
- **Error Handling**: No discussion of graceful degradation when solutions fail
---
## QEMU TESTING FRAMEWORK (2025-09-16)
### Overview
This section outlines comprehensive test criteria for validating RMT interrupt solutions using QEMU emulation on ESP32-S3 and ESP32-C3 platforms. QEMU provides a controlled environment to reproduce Wi-Fi interference patterns and measure interrupt latency without physical hardware variations.
### Test Infrastructure Requirements
#### QEMU Setup Commands
```bash
# Install QEMU for ESP32 emulation
uv run ci/install-qemu.py
# Run QEMU tests with ESP32-S3 (Xtensa)
uv run test.py --qemu esp32s3
# Run QEMU tests with ESP32-C3 (RISC-V)
uv run test.py --qemu esp32c3
# Skip QEMU installation if already present
FASTLED_QEMU_SKIP_INSTALL=true uv run test.py --qemu esp32s3
```
#### Platform Coverage Matrix
| Platform | Architecture | RMT Version | Max Official Priority | Max Custom Priority | Test Priority |
|----------|-------------|-------------|----------------------|-------------------|---------------|
| ESP32-S3 | Xtensa LX7 | RMT5 | 3 | 5 | High |
| ESP32-C3 | RISC-V | RMT5 | 3 | 7 | High |
| ESP32-C6 | RISC-V | RMT5 | 3 | 7 | Medium |
| ESP32 | Xtensa LX6 | RMT4 | N/A | N/A | Reference |
### Test Scenarios
#### 1. Baseline RMT Timing Tests
**Objective**: Establish baseline RMT performance without interference
**Test Cases**:
- **T1.1**: RMT5 default priority (level 3) LED refresh timing
- **T1.2**: Memory buffer utilization patterns (single vs double block)
- **T1.3**: WS2812B timing compliance under no-load conditions
- **T1.4**: Multiple channel simultaneous operation
**Success Criteria**:
- WS2812B timing within ±150ns tolerance
- Zero dropped frames for 300 LED array
- Consistent ~35µs refresh intervals
- No interrupt overruns in baseline conditions
**QEMU Implementation**:
```cpp
// Test harness in tests/qemu_rmt_baseline.cpp
void test_rmt_baseline_timing() {
// Configure RMT channel with default priority 3
rmt_channel_config_t config = {
.gpio_num = TEST_GPIO_PIN,
.clk_div = 2,
.mem_block_num = 1, // Single block baseline
.intr_priority = 3 // Official maximum
};
// Measure timing consistency across 1000 refresh cycles
for (int i = 0; i < 1000; i++) {
uint64_t start = esp_timer_get_time();
fastled_rmt_refresh_leds(test_pattern, 300);
uint64_t elapsed = esp_timer_get_time() - start;
// Verify timing bounds
assert(elapsed >= 34500 && elapsed <= 35500); // ±500µs tolerance
}
}
```
#### 2. Wi-Fi Interference Simulation
**Objective**: Reproduce the Wi-Fi starvation issue that causes RMT5 flickering
**Test Cases**:
- **T2.1**: Simulated Wi-Fi interrupt bursts at level 4 priority
- **T2.2**: Sustained Wi-Fi traffic during LED refresh cycles
- **T2.3**: Timing jitter measurement under interference
- **T2.4**: Buffer underrun detection and quantification
**QEMU Wi-Fi Simulation**:
```cpp
// Simulate Wi-Fi interrupt load in QEMU
void simulate_wifi_interference() {
// Install level 4 interrupt to simulate Wi-Fi
esp_intr_alloc(ETS_WIFI_MAC_INTR_SOURCE,
ESP_INTR_FLAG_LEVEL4 | ESP_INTR_FLAG_IRAM,
wifi_interference_isr, NULL, &wifi_handle);
// Generate periodic 40-50µs interrupt bursts
esp_timer_create(&wifi_timer_config, &wifi_timer);
esp_timer_start_periodic(wifi_timer, 100); // 100µs period
}
void IRAM_ATTR wifi_interference_isr(void* arg) {
// Simulate 40µs Wi-Fi processing time
uint64_t start = esp_timer_get_time();
while ((esp_timer_get_time() - start) < 40) {
// Busy wait to consume CPU time
__asm__ volatile ("nop");
}
}
```
**Success Criteria for Problem Reproduction**:
- Measured jitter > ±10µs with Wi-Fi simulation active
- Buffer underruns detected in RMT interrupt handler
- Visual flicker correlation with Wi-Fi interrupt timing
#### 3. High-Priority Interrupt Solution Validation
**Objective**: Test level 4/5 interrupt priority solutions
**Test Cases**:
- **T3.1**: ESP32-S3 level 4 interrupt implementation (experimental)
- **T3.2**: ESP32-S3 level 5 interrupt with assembly shim
- **T3.3**: ESP32-C3 level 4-7 interrupt testing (RISC-V advantage)
- **T3.4**: Interrupt latency measurement and comparison
**Implementation Reference**: `src/platforms/esp/32/interrupts/esp32_s3.hpp:149-161`
**Test Implementation**:
```cpp
// Test level 5 interrupt solution (ESP32-S3 only)
void test_level5_interrupt_solution() {
// Use custom high-priority RMT initialization
esp_err_t err = fastled_esp32s3_rmt_init_custom(
RMT_CHANNEL_0,
TEST_GPIO_PIN,
40000000, // 40MHz resolution
128, // Single block
5 // Level 5 priority - EXPERIMENTAL
);
assert(err == ESP_OK);
// Activate Wi-Fi interference simulation
simulate_wifi_interference();
// Measure timing stability with level 5 priority
for (int i = 0; i < 1000; i++) {
uint64_t start = esp_timer_get_time();
fastled_rmt_refresh_leds(test_pattern, 300);
uint64_t elapsed = esp_timer_get_time() - start;
// Level 5 should maintain tight timing despite Wi-Fi
assert(elapsed >= 34800 && elapsed <= 35200); // ±200µs tolerance
}
}
```
#### 4. Double-Buffer Solution Validation
**Objective**: Test ping-pong buffering approaches (limited by LED strip API)
**Test Cases**:
- **T4.1**: Memory block doubling via `mem_block_symbols` parameter
- **T4.2**: Custom low-level RMT implementation with true ping-pong
- **T4.3**: Buffer refill timing analysis
- **T4.4**: Comparison with RMT4 dual-buffer behavior
**Critical Finding**: LED strip API limitation prevents true ping-pong (`RMT.md:254-258`)
**Test Implementation**:
```cpp
// Test doubled memory blocks (not true ping-pong)
void test_doubled_memory_blocks() {
rmt_channel_config_t config = {
.gpio_num = TEST_GPIO_PIN,
.clk_div = 2,
.mem_block_num = 2, // Double the memory
.intr_priority = 3
};
// This creates larger buffer, NOT ping-pong
// Test if larger buffer helps with Wi-Fi jitter
simulate_wifi_interference();
// Measure improvement (if any)
uint32_t underrun_count = 0;
for (int i = 0; i < 1000; i++) {
if (fastled_rmt_refresh_with_underrun_detection(test_pattern, 300)) {
underrun_count++;
}
}
// Expect fewer underruns than single block, but not elimination
printf("Double block underruns: %lu (should be < single block)\n", underrun_count);
}
```
#### 5. Cross-Architecture Comparison Tests
**Objective**: Compare RMT behavior between Xtensa and RISC-V cores
**Test Cases**:
- **T5.1**: Identical test patterns on ESP32-S3 (Xtensa) vs ESP32-C3 (RISC-V)
- **T5.2**: Interrupt latency differences between architectures
- **T5.3**: Assembly shim requirement validation (Xtensa only)
- **T5.4**: Priority level effectiveness comparison
**Architecture-Specific Considerations**:
- **Xtensa (ESP32-S3)**: Requires assembly shims for level 4+ (`esp32_s3.hpp:316-368`)
- **RISC-V (ESP32-C3)**: C handlers work at all priority levels (`esp32_c3_c6.hpp:180-194`)
### Automated Test Execution
#### Test Harness Integration
```bash
# Add to existing test framework
uv run test.py RMTInterruptValidation --qemu esp32s3
uv run test.py RMTInterruptValidation --qemu esp32c3
# Specific test scenarios
uv run test.py RMTBaseline --qemu esp32s3
uv run test.py RMTWiFiInterference --qemu esp32s3
uv run test.py RMTHighPriority --qemu esp32s3
uv run test.py RMTDoubleBuffer --qemu esp32s3
```
#### Expected Test Results
| Test Scenario | ESP32-S3 Expected | ESP32-C3 Expected | Pass/Fail Criteria |
|---------------|-------------------|-------------------|-------------------|
| T1.1 Baseline | ±200µs jitter | ±200µs jitter | PASS if within tolerance |
| T2.1 Wi-Fi Interference | ±10-50µs jitter | ±10-50µs jitter | PASS if problem reproduced |
| T3.1 Level 4 Priority | ±500µs improvement | ±500µs improvement | PASS if jitter reduced |
| T3.2 Level 5 Priority | ±1000µs improvement | N/A (max level 7) | PASS if jitter eliminated |
| T4.1 Double Buffer | Partial improvement | Partial improvement | PASS if some reduction |
#### Performance Benchmarks
**Target Metrics**:
- **Baseline RMT5**: 35±10µs refresh time (no Wi-Fi)
- **RMT5 + Wi-Fi**: 35±50µs refresh time (problem case)
- **Level 5 Priority**: 35±2µs refresh time (solution target)
- **Double Buffer**: 35±20µs refresh time (partial solution)
### Test Data Collection
#### Logging and Metrics
```cpp
// Timing data collection structure
typedef struct {
uint64_t timestamp;
uint32_t refresh_time_us;
uint32_t interrupt_latency_us;
uint8_t priority_level;
bool wifi_active;
bool buffer_underrun;
} rmt_timing_sample_t;
// Collect 10,000 samples per test scenario
#define TIMING_SAMPLES 10000
rmt_timing_sample_t timing_data[TIMING_SAMPLES];
```
#### Statistical Analysis
- **Mean refresh time** and standard deviation
- **95th percentile latency** measurements
- **Buffer underrun frequency** (occurrences per 1000 refreshes)
- **Jitter distribution** histograms
### QEMU Limitations and Considerations
#### QEMU Timing Accuracy
- QEMU provides relative timing comparison, not absolute accuracy
- Interrupt priority simulation may not perfectly match hardware
- Focus on comparative analysis between configurations
#### Hardware Validation Requirements
- QEMU results must be validated on physical ESP32-S3/C3 hardware
- Real Wi-Fi interference patterns may differ from simulation
- Oscilloscope measurements required for WS2812B timing verification
---
## FEASIBILITY AUDIT (2025-09-16)
### Executive Summary
After analyzing the FastLED RMT5 driver implementation and ESP32 IDF documentation, **both proposed solutions are technically feasible**. The ping-pong buffering approach is recommended as the primary solution due to its simplicity and proven effectiveness.
### Core Architecture Analysis
#### ESP32 Variants and Impact
- **ESP32/ESP32-S3**: Xtensa LX6/LX7 cores - Support level 5 interrupts but require ASM shims
- **ESP32-C3/C6**: RISC-V cores - Different interrupt architecture but same priority levels
- **Impact on RMT**: Core architecture affects interrupt entry overhead (~10-15% difference) but doesn't fundamentally change the Wi-Fi starvation issue
### Solution 1: High-Priority Interrupt (Level 5)
#### Current State
- FastLED RMT5 currently uses default priority 3 (found in `strip_rmt.cpp:31`)
- Wi-Fi typically runs at priority 4, causing starvation
- Infrastructure exists: `interrupt_priority` parameter already plumbed through (`strip_rmt.h:23`)
#### Implementation Requirements
1. **Xtensa ASM Shim** (ESP32/S3):
```asm
; Required for level 5 interrupts on Xtensa
; Must save/restore registers manually
; Call C handler with proper stack alignment
```
2. **IRAM Safety**: All ISR code must be marked `IRAM_ATTR`
3. **Configuration**: Set `CONFIG_RMT_ISR_IRAM_SAFE=y` in IDF
#### Feasibility: **MEDIUM COMPLEXITY**
- ✅ Technically possible - IDF supports priority levels 1-5
- ✅ Infrastructure partially exists
- ⚠️ Requires platform-specific ASM code
- ⚠️ Risk of system instability if not done correctly
- ⚠️ Cannot change priority after initialization
### Solution 2: Ping-Pong Buffering (Double Memory Blocks)
#### Current State
- RMT5 uses ESP-IDF's `led_strip` API which abstracts away direct RMT control
- `mem_block_symbols` parameter increases buffer size but **NOT true ping-pong**
- RMT4 had direct access to RMT hardware registers for manual buffer switching
- LED strip API handles encoding internally, no access to `fillNext()` mechanism
#### Critical Issue: **LED Strip API Limitation**
The `led_strip` API in IDF v5 doesn't expose ping-pong buffer control:
- RMT4: Direct hardware access, manual buffer refill via `fillNext()` interrupt
- RMT5: High-level API, encoder runs in ISR but no manual buffer control
- Increasing `mem_block_symbols` just makes a larger single buffer
- **This is NOT equivalent to RMT4's dual-buffer ping-pong**
#### Implementation Challenges
1. **No Manual Buffer Control**:
```cpp
// RMT4 had this in interrupt handler:
pController->fillNext(true); // Manually refill next buffer half
// RMT5 LED strip API has no equivalent
led_strip_refresh_async(); // Fire and forget, no buffer control
```
2. **Encoder Abstraction**:
- Encoder callback runs automatically in ISR context
- No way to manually trigger buffer refills
- Can't implement true ping-pong without bypassing LED strip API
#### Feasibility: **COMPLEX - REQUIRES MAJOR REFACTOR**
- ❌ NOT a simple one-line change
- ❌ LED strip API doesn't support true ping-pong
- ⚠️ Would need to bypass LED strip API entirely
- ⚠️ Requires reimplementing low-level RMT control like RMT4
- ⚠️ Loses benefits of IDF's maintained LED strip driver
### Memory Impact Analysis
#### Current Memory Usage
- **Without DMA**: 48-64 symbols × 4 bytes = 192-256 bytes per channel
- **With DMA**: 1024 symbols × 4 bytes = 4KB per channel
#### With Double Buffering
- **Without DMA**: 384-512 bytes per channel (+192-256 bytes)
- **With DMA**: No change (already large)
For typical 8-channel setup: +1.5-2KB total RAM usage
### Testing Validation Required
1. **Reproduce Issue**:
- Enable Wi-Fi AP mode + web server
- Drive 300+ LEDs on multiple pins
- Measure flicker frequency with oscilloscope
2. **Validate Fix**:
- Test both solutions independently
- Measure interrupt latency improvement
- Verify no regressions on non-Wi-Fi use cases
### Implementation Priority
1. **Phase 1 (Immediate)**: Implement ping-pong buffering
- Low risk, high reward
- Can ship in patch release
2. **Phase 2 (Future)**: Consider high-priority ISR for extreme cases
- Only if ping-pong proves insufficient
- Requires extensive platform testing
### Configuration Recommendations
Add these defines to allow user tuning:
```cpp
#define FASTLED_RMT5_MEM_BLOCKS_MULTIPLIER 2 // Double buffer by default
#define FASTLED_RMT5_INTERRUPT_PRIORITY 3 // Keep default, allow override
#define FASTLED_RMT5_FORCE_IRAM_SAFE 0 // Optional IRAM safety
```
### Solution 3: Bypass LED Strip API (Use Low-Level RMT)
#### Approach
- Abandon `led_strip` API entirely
- Use IDF v5's low-level RMT TX API directly
- Implement custom encoder with manual buffer management
- Port RMT4's `fillNext()` logic to IDF v5 RMT API
#### Implementation Requirements
```cpp
// Use raw RMT API instead of led_strip
rmt_new_tx_channel(&config, &channel);
rmt_new_bytes_encoder(&encoder_config, &encoder);
// Implement custom ISR with buffer refill logic
```
#### Feasibility: **MODERATE - MOST VIABLE**
- ✅ Gives full control over buffer management
- ✅ Can implement true ping-pong like RMT4
- ⚠️ Requires significant code rewrite
- ⚠️ Must maintain custom encoder implementation
- ✅ Proven approach (RMT4 uses this successfully)
### Revised Conclusion
**The initial assessment was incorrect.** The LED strip API abstraction in RMT5 fundamentally prevents implementing true ping-pong buffering as a "simple one-line change."
**Recommended approach:**
1. **Immediate**: Increase interrupt priority (Solution 1) - simplest to implement
2. **Short-term**: Bypass LED strip API and use low-level RMT (Solution 3) - most effective
3. **Long-term**: Work with Espressif to add ping-pong support to LED strip API
The flickering issue is real and significant, but the solution requires more substantial changes than initially thought. Simply increasing `mem_block_symbols` only creates a larger buffer, not the dual-buffer ping-pong mechanism that made RMT4 resistant to Wi-Fi interference.
---
## INTERRUPT IMPLEMENTATION ANALYSIS (2025-09-16)
### Overview of Current FastLED Interrupt Infrastructure
Based on analysis of the interrupt header files (`src/platforms/esp/32/interrupts/`), FastLED has already implemented comprehensive infrastructure for high-priority interrupt handling on both Xtensa (ESP32-S3) and RISC-V (ESP32-C3/C6) architectures.
### Architecture-Specific Implementations
#### ESP32-S3 (Xtensa LX7) - `esp32_s3.hpp`
**Key Capabilities**:
- **Official Support**: Priority levels 1-3 via `rmt_tx_channel_config_t`
- **Experimental Support**: Priority levels 4-5 with custom assembly shims
- **Assembly Trampoline Macro**: `FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE` (`esp32_s3.hpp:316-368`)
- **Interrupt Installation Functions**: Pre-built helpers for level 3-5 priority installation
**Critical Requirements for Level 4-5 Implementation**:
```cpp
// Level 5 interrupt requires assembly shim (lines 149-161)
extern void xt_highint5(void);
void IRAM_ATTR fastled_esp32s3_level5_handler(void);
// Installation helper already available
esp_err_t fastled_esp32s3_install_level5_interrupt(
int source, void *arg, esp_intr_handle_t *handle);
```
**Build Configuration Required**:
- `CONFIG_RMT_ISR_IRAM_SAFE=y`
- Assembly `.S` files in build system
- `-mlongcalls` compiler flag
#### ESP32-C3/C6 (RISC-V) - `esp32_c3_c6.hpp`
**Key Advantages**:
- **Simplified Implementation**: C handlers work at any priority level (1-7)
- **No Assembly Required**: Standard `IRAM_ATTR` functions sufficient
- **PLIC Integration**: Platform-Level Interrupt Controller handles arbitration
- **Maximum Priority**: Level 7 (vs. Xtensa level 5)
**Implementation Benefits**:
```cpp
// RISC-V can use C handlers directly at high priority
void IRAM_ATTR fastled_riscv_experimental_handler(void *arg);
// Simpler installation (lines 286-292)
esp_err_t fastled_riscv_install_interrupt(
int source, int priority, void (*handler)(void *),
void *arg, esp_intr_handle_t *handle);
```
### Testing Infrastructure Integration
#### Current Test Framework Compatibility
The interrupt implementations are designed to integrate with the existing QEMU test infrastructure:
```bash
# ESP32-S3 Xtensa testing
uv run test.py --qemu esp32s3
# ESP32-C3 RISC-V testing
uv run test.py --qemu esp32c3
```
#### Proposed Test Case Implementation
Based on the interrupt header analysis, the test framework should include:
1. **Level 3 Baseline Tests** (Official FastLED support)
- Verify standard `rmt_tx_channel_config_t` integration
- Test maximum officially supported priority
- Validate against existing FastLED RMT driver
2. **Level 4-5 Experimental Tests** (ESP32-S3)
- Test assembly shim functionality (`FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE`)
- Validate custom RMT initialization bypassing official driver
- Measure Wi-Fi interference immunity
3. **Level 4-7 RISC-V Tests** (ESP32-C3/C6)
- Test C handler high-priority interrupts
- Compare RISC-V vs Xtensa interrupt latency
- Validate PLIC priority arbitration
### Implementation Readiness Assessment
#### What's Already Available
**✅ Complete Infrastructure**:
- Assembly trampoline macros for Xtensa high-priority interrupts
- C handler support for RISC-V high-priority interrupts
- Interrupt installation helper functions for both architectures
- Build configuration documentation
- IRAM safety considerations documented
**✅ Test Integration Points**:
- QEMU support for both ESP32-S3 and ESP32-C3
- Existing test framework can be extended
- Performance measurement infrastructure available
#### What Needs Implementation
**🔨 RMT Driver Integration**:
- `fastled_esp32s3_rmt_init_custom()` function implementation
- `fastled_riscv_rmt_init_experimental()` function implementation
- Bridge between interrupt infrastructure and RMT hardware configuration
**🔨 Custom RMT Control**:
- Bypass LED strip API for true ping-pong buffering
- Low-level RMT register manipulation
- Custom encoder implementation for buffer management
### Recommended Implementation Sequence
#### Phase 1: High-Priority Interrupt (Ready to Implement)
```cpp
// ESP32-S3 Level 5 Implementation - infrastructure exists
void IRAM_ATTR rmt_level5_handler(void *arg) {
// Custom RMT buffer refill logic
// Bypass official LED strip API
}
// Use existing trampoline macro
FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE(rmt_level5_isr, rmt_level5_handler)
// Install with existing helper
fastled_esp32s3_install_level5_interrupt(ETS_RMT_INTR_SOURCE, NULL, &handle);
```
#### Phase 2: Custom RMT Implementation
```cpp
// Bypass led_strip API entirely - use raw RMT API
rmt_new_tx_channel(&channel_config, &channel);
rmt_new_bytes_encoder(&encoder_config, &encoder);
// Implement custom buffer management
// Port RMT4 fillNext() logic to IDF v5 RMT API
```
#### Phase 3: QEMU Validation
- Implement test cases in `tests/qemu_rmt_interrupts.cpp`
- Validate both Xtensa and RISC-V implementations
- Performance comparison and regression testing
### Risk Assessment
**Low Risk (High-Priority Interrupts)**:
- Infrastructure already exists and documented
- Clear implementation path with existing helpers
- QEMU testing available for validation
**Medium Risk (Custom RMT Implementation)**:
- Requires bypassing official LED strip API
- Must maintain custom encoder implementation
- Compatibility with future IDF versions uncertain
**Critical Success Factors**:
1. **IRAM Safety**: All high-priority code must be IRAM-resident
2. **Timing Validation**: QEMU + hardware oscilloscope verification
3. **Regression Testing**: Ensure no impact on normal priority operation
4. **Documentation**: Clear usage guidelines for different priority levels
### Conclusion
The interrupt infrastructure analysis reveals that FastLED already has **comprehensive, production-ready infrastructure** for high-priority interrupt implementation on both Xtensa and RISC-V architectures. The main implementation work required is:
1. **Integration layer** between interrupt infrastructure and RMT hardware
2. **Custom RMT driver** that bypasses LED strip API limitations
3. **Test validation** using existing QEMU framework
This significantly reduces the implementation complexity compared to the initial assessment. The high-priority interrupt solution is **immediately implementable** using existing infrastructure, while the ping-pong buffering solution requires the more substantial custom RMT driver development.

View File

@@ -0,0 +1,261 @@
# FastLED TEST_CASE Execution Guide
## Overview
FastLED uses the **doctest** framework for C++ unit testing. Tests are organized in files named `test_*.cpp` in the `tests/` directory. Each file can contain multiple `TEST_CASE` macros that define individual test functions.
## Test Structure
- **Test Files**: Located in `tests/test_*.cpp` (e.g., `test_algorithm.cpp`, `test_easing.cpp`)
- **TEST_CASEs**: Individual test functions defined with `TEST_CASE("name")` macro
- **SUBCASEs**: Nested test sections within TEST_CASEs using `SUBCASE("name")`
- **Assertions**: `CHECK()`, `CHECK_EQ()`, `CHECK_LT()`, etc. for validation
## Quick Start
### Using the MCP Server (Recommended)
1. **Start the MCP server:**
```bash
uv run mcp_server.py
```
2. **Available MCP tools:**
- `run_tests`: Run tests with various options
- `list_test_cases`: List available TEST_CASEs
- `test_instructions`: Show detailed instructions
### Command Line Usage
1. **Run all tests:**
```bash
uv run test.py
# or using the user rule:
bash test
```
2. **Run only C++ tests:**
```bash
uv run test.py --cpp
```
3. **Run specific test file:**
```bash
uv run test.py --cpp algorithm
# This runs test_algorithm.cpp
```
## Running Specific TEST_CASEs
### Method 1: Direct Executable (Most Precise)
First, compile the tests, then run the specific executable with doctest filtering:
```bash
# 1. Compile the test (if not already compiled)
uv run test.py --cpp algorithm
# 2. Run specific TEST_CASE
./tests/.build/bin/test_algorithm --test-case="reverse an int list"
# 3. Run with verbose output
./tests/.build/bin/test_algorithm --test-case="reverse an int list" --verbose
```
### Method 2: Using MCP Server
Use the `run_tests` tool with specific parameters:
- `test_type`: "specific"
- `specific_test`: "algorithm" (without test_ prefix)
- `test_case`: "reverse an int list"
- `verbose`: true (optional)
### Method 3: List Available TEST_CASEs First
```bash
# Using MCP list_test_cases tool, or manually:
grep -r "TEST_CASE" tests/test_algorithm.cpp
```
## Common Test Files and Their Purpose
| File | Purpose | Example TEST_CASEs |
|------|---------|-------------------|
| `test_algorithm.cpp` | Algorithm utilities | "reverse an int list" |
| `test_easing.cpp` | Easing functions | "8-bit easing functions", "easeInOutQuad16" |
| `test_hsv16.cpp` | HSV color space | "HSV to RGB conversion", "HSV hue wraparound" |
| `test_math.cpp` | Mathematical functions | "sin32 accuracy", "random number generation" |
| `test_vector.cpp` | Vector container | "vector push_back", "vector iteration" |
| `test_fx.cpp` | Effects framework | "fx engine initialization" |
| `test_apa102_hd.cpp` | APA102 HD driver | "APA102 color mapping" |
| `test_bitset.cpp` | Bitset operations | "bitset operations" |
## Example Workflows
### Debug a Failing Test
1. **List TEST_CASEs in a specific file:**
```bash
# Using MCP server list_test_cases tool with test_file: "easing"
```
2. **Run with verbose output:**
```bash
uv run test.py --cpp easing --verbose
```
3. **Run specific failing TEST_CASE:**
```bash
./tests/.build/bin/test_easing --test-case="8-bit easing functions" --verbose
```
### Test Development Workflow
1. **Clean build and run:**
```bash
uv run test.py --cpp mytest --clean --verbose
```
2. **Run only failed tests:**
```bash
cd tests/.build && ctest --rerun-failed
```
3. **Test with different compilers:**
```bash
uv run test.py --cpp mytest --clang
```
### Finding Specific Tests
1. **Search for TEST_CASEs by pattern:**
```bash
# Using MCP list_test_cases tool with search_pattern: "easing"
```
2. **Manual search:**
```bash
grep -r "TEST_CASE.*easing" tests/
```
## Doctest Command Line Options
When running test executables directly, you can use these doctest options:
- `--test-case=<name>`: Run specific TEST_CASE
- `--test-case-exclude=<name>`: Exclude specific TEST_CASE
- `--subcase=<name>`: Run specific SUBCASE
- `--subcase-exclude=<name>`: Exclude specific SUBCASE
- `--list-test-cases`: List all TEST_CASEs in the executable
- `--list-test-suites`: List all test suites
- `--verbose`: Show detailed output including successful assertions
- `--success`: Show successful assertions too
- `--no-colors`: Disable colored output
- `--force-colors`: Force colored output
- `--no-breaks`: Disable breaking into debugger
- `--no-skip`: Don't skip any tests
### Examples:
```bash
# List all TEST_CASEs in a test file
./tests/.build/bin/test_easing --list-test-cases
# Run multiple TEST_CASEs with pattern
./tests/.build/bin/test_easing --test-case="*quad*"
# Exclude specific TEST_CASEs
./tests/.build/bin/test_easing --test-case-exclude="*16-bit*"
# Run with full verbose output
./tests/.build/bin/test_easing --success --verbose
```
## Understanding Test Output
### Successful Test:
```
[doctest] run with "--help" for options
===============================================================================
TEST_CASE: reverse an int list
===============================================================================
tests/test_algorithm.cpp:12
all tests passed!
===============================================================================
[doctest] test cases: 1 | 1 passed | 0 failed | 0 skipped
[doctest] assertions: 5 | 5 passed | 0 failed |
[doctest] Status: SUCCESS!
```
### Failed Test:
```
===============================================================================
TEST_CASE: reverse an int list
===============================================================================
tests/test_algorithm.cpp:12
tests/test_algorithm.cpp:20: ERROR: CHECK_EQ(vec[0], 6) is NOT correct!
values: CHECK_EQ(5, 6)
===============================================================================
[doctest] test cases: 1 | 0 passed | 1 failed | 0 skipped
[doctest] assertions: 5 | 4 passed | 1 failed |
[doctest] Status: FAILURE!
```
## Tips and Best Practices
1. **Use `--verbose`** to see detailed test output and assertions
2. **Use `--clean`** when testing after significant code changes
3. **List TEST_CASEs first** to see what's available before running specific tests
4. **Individual TEST_CASE execution** is useful for debugging specific functionality
5. **Check test output carefully** - doctest provides detailed failure information with line numbers
6. **Use pattern matching** with `--test-case="*pattern*"` to run related tests
7. **Combine with debugger** - tests are compiled with debug symbols (`-g3`)
## Environment Setup
- Tests are compiled with Debug flags (`-g3`, `-O0`) for better debugging
- GDB integration available for crash analysis
- Static analysis warnings enabled as errors
- Cross-platform support (Linux, macOS, Windows)
- Uses zig/clang as the default compiler for consistency
## Troubleshooting
### Common Issues:
1. **Test executable not found:**
```bash
# Make sure tests are compiled first
uv run test.py --cpp <test_name>
```
2. **TEST_CASE name not found:**
```bash
# List available TEST_CASEs
./tests/.build/bin/test_<name> --list-test-cases
```
3. **Compilation errors:**
```bash
# Clean build
uv run test.py --cpp <test_name> --clean
```
4. **Permission denied on executable:**
```bash
chmod +x tests/.build/bin/test_<name>
```
## Integration with Development
### Git Hooks
Consider adding a pre-commit hook to run relevant tests:
```bash
#!/bin/bash
# Run tests for changed files
uv run test.py --cpp
```
### IDE Integration
Most IDEs can be configured to run individual TEST_CASEs directly from the editor by configuring the test executable path and doctest arguments.

View File

@@ -0,0 +1,47 @@
# Testing
* REPO SYNC MODE: When there is a compiler error there isn't a hard failure. The compiler error needs to get propagate back to the top.
* Esp32 testing
* https://github.com/marketplace/actions/esp32-qemu-runner will run a sketch for X seconds and see's if it crashes
* There's specific tests we'd like to run with this including the WS2812 and APA102 tests to test the clockless and clocked drivers
# Feature Enhancements
[ ] Adafruit converter driver
[ ] NeoPixel converter driver
* I2S driver for ESP32 WS2812
* https://github.com/hpwit/I2SClocklessLedDriver
* Our copy is here: https://github.com/FastLED/FastLED/blob/master/src/platforms/esp/32/clockless_i2s_esp32.h
* S3:
* https://github.com/hpwit/I2SClockLessLedDriveresp32s3
* Apparently, this driver allows MASSIVE parallelization for WS2812
* Timing guide for reducing RMT frequency https://github.com/Makuna/NeoPixelBus/pull/795
* ESp32 LED guide
* web: https://components.espressif.com/components/espressif/led_strip
* repo: https://github.com/espressif/idf-extra-components/tree/60c14263f3b69ac6e98ecae79beecbe5c18d5596/led_strip
* adafruit conversation on RMT progress: https://github.com/adafruit/Adafruit_NeoPixel/issues/375
* MIT Licensed SdFat library
* https://github.com/greiman/SdFat
* YVes LittleFS implementation for ESP
* https://github.com/hpwit/ledOS/blob/main/src/fileSystem.h
* NimBLE for Arduino
* https://github.com/h2zero/NimBLE-Arduino?tab=readme-ov-file
* Arduino test compile
* https://github.com/hpwit/arduino-test-compile/blob/master/arduino-test-compile.sh
# Misc:
* sutaburosu's guide to playing around with FastLED 4
* https://github.com/sutaburosu/FastLED4-ESP32-playpen
# Animartrix: try using sinf, cosf and other found trip instead of trying to go
full double precision.

View File

@@ -0,0 +1,352 @@
# Video Recording Feature for FastLED Web App
## Implementation Status: ✅ COMPLETE - FULLY IMPLEMENTED
**Last Updated**: September 18, 2025
**Commit**: Refactored video recording code to fix bugs and improve reliability
### Overview
A video recording feature has been implemented for the FastLED web application in `src/platforms/wasm/compiler/`. This feature allows users to capture both the canvas animations and audio output to create high-quality video recordings.
## Features Implemented
### 1. Recording Button UI
- **Location**: Upper right corner of the canvas
- **Visual States**:
- **Idle**: Gray circular button with a record icon (⚫)
- **Recording**: Red pulsing button with a stop icon (⬛)
- **Animation**: Smooth pulse effect when recording
- **Note**: Stdout button should be moved to upper left of canvas to avoid conflict with record button placement
### 2. Video Capture Specifications
- **Default Format**: MP4 with H.264 video codec and AAC audio codec
- **Fallback Formats**: WebM with VP9/VP8 video codec and Opus audio codec (auto-detected based on browser support)
- **Video Bitrate**: 10 Mbps (high quality)
- **Audio Bitrate**: 128 kbps
- **Frame Rate**: 30 FPS default (configurable to 60 FPS)
- **Resolution**: Captures at canvas native resolution
- **Frame Logging**: Each frame insertion is logged to console (e.g., "frame 16")
- **Automatic File Extension**: Correct file extension (.mp4 or .webm) based on selected codec
- **Recording Statistics**: Logs frame count, duration, FPS, and file size upon completion
- **Improved Error Handling**: Graceful fallback when MediaRecorder is unavailable
### 3. Audio Capture
- **Source**: Web Audio API AudioContext
- **Integration**: Automatically captures audio if AudioContext is available
- **MP3 Player Integration**:
- Captures audio from the existing MP3 player module
- Audio streams from MP3 playback are routed through the AudioContext
- Mixed audio (synthesized + MP3) is captured in the recording
- **Audio Mixing**: Combines all audio sources (synthesized sounds + MP3 playback) into a single stream
- **Fallback**: Records video-only if audio is unavailable
- **Sync**: Audio and video are automatically synchronized
### 4. User Interaction
- **Start Recording**: Click the record button or press `Ctrl+R` / `Cmd+R`
- **Stop Recording**: Click the button again (now showing stop icon)
- **File Save**: Automatic download prompt when recording stops
- **Filename Format**: `fastled-recording-YYYY-MM-DD-HH-MM-SS.{mp4|webm}` (extension matches codec)
- **Graceful Degradation**: Record button automatically hidden if MediaRecorder unsupported
## Frame Handling & Encoder Optimizations
### Dropped Frame Detection & Recovery
The video recording system implements comprehensive dropped frame handling:
#### Detection Methods
- **Frame Counter Monitoring**: Tracks expected vs. actual frame sequence
- **Timestamp Analysis**: Compares MediaRecorder timestamps with canvas refresh rate
- **Performance Observer**: Monitors frame presentation delays and skips
#### Recovery Strategies
```javascript
// Dropped frame detection
let expectedFrameCount = 0;
let actualFrameCount = 0;
const frameDropThreshold = 2; // Allow 2 consecutive drops before intervention
mediaRecorder.addEventListener('dataavailable', (event) => {
actualFrameCount++;
const timestamp = performance.now();
const expectedTimestamp = startTime + (expectedFrameCount * (1000 / fps));
if (timestamp - expectedTimestamp > frameDropThreshold * (1000 / fps)) {
console.warn(`Dropped frames detected: expected ${expectedFrameCount}, got ${actualFrameCount}`);
// Trigger recovery actions
handleDroppedFrames(expectedFrameCount - actualFrameCount);
}
console.log(`frame ${actualFrameCount}`);
expectedFrameCount++;
});
function handleDroppedFrames(droppedCount) {
// Option 1: Duplicate last frame to maintain timing
// Option 2: Reduce capture frame rate temporarily
// Option 3: Skip frame interpolation for smooth playback
}
```
### Duplicate Frame Optimization
#### H.264 Encoder Optimizations
The H.264 encoder in MP4 format provides several optimizations for static or repetitive content:
- **P-frames (Predicted frames)**: Encode only differences from previous frames
- **B-frames (Bidirectional frames)**: Reference both previous and future frames for maximum compression
- **Temporal Compression**: Automatically detects static regions and encodes efficiently
- **Rate Control**: Adjusts bitrate dynamically based on content complexity
#### Implementation Details
```javascript
// Configure encoder for optimal duplicate frame handling
const encoderConfig = {
mimeType: 'video/mp4;codecs=h264,aac',
videoBitsPerSecond: 10000000,
audioBitsPerSecond: 128000,
// H.264 specific optimizations
keyFrameInterval: 30, // I-frame every 30 frames (1 second at 30fps)
frameSkipping: true, // Allow encoder to skip identical frames
contentHint: 'motion' // Optimize for animation content
};
// Monitor encoding efficiency
mediaRecorder.addEventListener('start', () => {
console.log('Encoder supports duplicate frame optimization: ',
mediaRecorder.mimeType.includes('h264'));
});
```
#### Fallback Encoder Behavior
When MP4/H.264 is not available, the system falls back to WebM/VP9:
- **VP9 Optimization**: Similar temporal compression but different algorithm
- **Opus Audio**: Efficient silence detection and compression
- **WebM Container**: Supports variable frame rates for dropped frame scenarios
#### Performance Monitoring
```javascript
// Track encoding performance
const encodingStats = {
framesEncoded: 0,
framesDuplicated: 0,
bytesGenerated: 0,
compressionRatio: 0
};
mediaRecorder.addEventListener('dataavailable', (event) => {
encodingStats.bytesGenerated += event.data.size;
encodingStats.framesEncoded++;
// Estimate duplicate frame optimization
const expectedSize = (canvas.width * canvas.height * 3) / 8; // RGB to bytes
const actualSize = event.data.size;
const efficiency = 1 - (actualSize / expectedSize);
if (efficiency > 0.8) {
encodingStats.framesDuplicated++;
console.log(`High compression frame ${encodingStats.framesEncoded} (${efficiency.toFixed(2)} efficiency)`);
}
});
```
## Technical Implementation
### Files Created/Modified
1. **`modules/video_recorder.js`** (NEW)
- Core VideoRecorder class
- MediaRecorder API integration
- Canvas stream capture
- Audio context integration with MP3 player support
- Mixed audio capture (MP3 + synthesized sounds)
- File save functionality
2. **`index.html`** (MODIFIED)
- Added record button HTML structure
- SVG icons for record/stop states
3. **`index.css`** (MODIFIED)
- Record button styling
- Animation effects (pulse, hover)
- State transitions
4. **`index.js`** (MODIFIED)
- Video recorder initialization
- Button event handling
- Keyboard shortcut support
- Global debug functions
## API Documentation
### MediaRecorder API Usage
```javascript
// Canvas capture
canvas.captureStream(fps) // Creates MediaStream from canvas
// Audio capture with MP3 mixing
audioContext.createMediaStreamDestination() // Creates audio destination node
// MP3 player audio routing
mp3Player.connect(audioDestination) // Route MP3 audio to recording destination
synthesizer.connect(audioDestination) // Route synthesized audio to destination
// Combined stream with mixed audio
new MediaStream([
...videoStream.getVideoTracks(),
...audioDestination.stream.getAudioTracks() // Contains mixed audio from all sources
])
// Recording with mixed audio
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/mp4;codecs=h264,aac', // Default: MP4 with H.264 video and AAC audio
videoBitsPerSecond: 10000000,
audioBitsPerSecond: 128000 // Captures all mixed audio sources
})
// Frame logging during recording
mediaRecorder.addEventListener('dataavailable', (event) => {
console.log(`frame ${++frameCounter}`); // Logs each frame insertion
});
```
### Key Design Decisions
1. **File Save Timing**: Files are saved AFTER recording stops (not when it starts)
- Ensures precise start/stop timing for A/V sync
- User sees save dialog after recording completes
- No filename prompt interrupts recording start
2. **High Bitrate Choice**: 10 Mbps video bitrate
- Ensures high quality capture
- Suitable for 60 FPS content
- Can be compressed later if needed
3. **MP4/H.264 Format**: Primary codec choice
- Universal browser support for high-quality video
- Good compression efficiency with AAC audio
- Supports both video and audio tracks
- WebM/VP9 fallback for browsers with limited MP4 support
## Usage Instructions
### For Users
1. Click the gray record button in the upper-right corner of the canvas
2. Button turns red and pulses to indicate recording
3. Perform your LED animation
4. Click the red button to stop recording
5. Browser will prompt to save the video file
### For Developers
#### Global Functions (Console Access)
```javascript
// Get recorder instance
window.getVideoRecorder()
// Start recording programmatically
window.startVideoRecording()
// Stop recording programmatically
window.stopVideoRecording()
```
#### Keyboard Shortcuts
- `Ctrl+R` / `Cmd+R`: Toggle recording on/off
## Browser Compatibility
- ✅ Chrome/Chromium (Full support)
- ✅ Firefox (Full support)
- ✅ Edge (Full support)
- ⚠️ Safari (Limited codec support, uses fallback)
## Recent Bug Fixes & Improvements
### September 18, 2025 Refactor
- **Fixed codec configuration**: Changed default from VP9 to H.264/MP4 for better compatibility
- **Improved audio routing**: Enhanced audio connection logic for more reliable audio capture
- **Dynamic file extensions**: Automatically uses correct extension (.mp4 or .webm) based on codec
- **Enhanced error handling**: Added graceful fallback when MediaRecorder API is unavailable
- **Better resource cleanup**: Improved disposal method to prevent memory leaks
- **Frame logging implementation**: Added proper frame counting and logging as documented
- **Recording statistics**: Added comprehensive logging of recording metrics (duration, FPS, file size)
- **Canvas content detection**: Improved blank canvas detection without interfering with actual content
- **UI positioning fix**: Moved stdout label to upper left to avoid conflicts with record button
### Bug Fixes Addressed
1. **Inconsistent codec defaults** - Now prioritizes H.264/MP4 over VP9/WebM
2. **Audio connection failures** - Improved audio routing with better error handling
3. **Wrong file extensions** - Files now use correct extension matching the codec
4. **Missing frame logging** - Frame counter now properly logs each frame
5. **Memory leaks** - Enhanced cleanup in disposal and error scenarios
6. **Canvas interference** - Removed test pattern drawing that could interfere with content
7. **MediaRecorder detection** - Added proper feature detection and graceful degradation
## Future Enhancements (Optional)
- [ ] Settings menu for quality presets
- [ ] Frame rate selector (30/60 FPS toggle)
- [ ] Recording timer display
- [ ] Pause/resume functionality
- [ ] Custom filename input
- [ ] Multiple format export options
- [ ] Recording preview before save
- [ ] Audio level meters for MP3 and synthesized audio
- [ ] Separate audio track controls (mute individual sources)
- [ ] Audio normalization for mixed sources
## Troubleshooting
### No Audio in Recording
- Check if browser has microphone permissions
- Verify AudioContext is initialized
- Some browsers require user interaction to start AudioContext
- Ensure MP3 player audio nodes are properly connected to the recording destination
- Verify that audio mixing is working (check browser console for audio node errors)
### Recording Button Not Appearing
- Check browser console for errors
- Verify canvas element exists with ID "myCanvas"
- Ensure JavaScript modules are loading correctly
### Low Quality Video
- Browser may be using fallback codec
- Check available disk space
- Try different browser for VP9 support
### Dropped Frames Issues
- **High CPU Usage**: Reduce canvas complexity or frame rate
- **Memory Pressure**: Monitor browser memory usage, restart if needed
- **Background Tabs**: Ensure recording tab has focus for optimal performance
- **Hardware Acceleration**: Enable GPU acceleration in browser settings
### Encoder Optimization Not Working
- **H.264 Unavailable**: Check browser codec support with `MediaRecorder.isTypeSupported()`
- **Poor Compression**: Verify content has static regions for optimization
- **Frame Skipping Disabled**: Some browsers may not support frameSkipping parameter
- **Content Hint Ignored**: Fallback to default encoding if contentHint not supported
## Testing Checklist
### Basic Functionality
- [x] Record button appears in correct position
- [x] Button changes color when recording
- [x] Pulse animation works during recording
- [x] Canvas content is captured correctly
- [x] Audio is captured (when available)
- [x] File saves with correct timestamp
- [x] Keyboard shortcut works
- [x] Multiple start/stop cycles work correctly
- [x] Memory is properly released after recording
### Frame Handling & Optimization
- [x] Frame logging appears in console during recording
- [x] Dropped frame detection triggers warnings when appropriate
- [x] Static content shows high compression efficiency
- [x] H.264 encoder optimization is active when supported
- [x] Fallback to WebM/VP9 works when MP4 unavailable
- [x] Performance monitoring tracks encoding statistics
- [x] Recovery strategies activate during frame drops
- [x] Variable frame rate handling works under load

View File

@@ -0,0 +1,57 @@
I was dissatisfied with a gamma corrected ramp using the APA102HD mode (it seemed to visibly step at one or more points), and some analysis of the 8 bit rgb + 5 bit brightness function showed why:
https://www.argyllcms.com/APA102_loglog.svg.
So here is a suggested replacement for five_bit_bitshift() in fl/five_bit_hd_gamma.cpp, that fixed the problem for me:
// Improved FastLED five_bit_bitshift() function
// This appears to exactly match the floating point reference,
// with a worst case resulting error of 0.2% over the full 16 bit input range,
// and an average error of 0.05% over that range. Errors scale with maximum
// magnitude.
// ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31
static uint32_t bright_scale[32] = {
0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097,
252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912,
126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986,
84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280
};
// Since the return value wasn't used, it has been omitted.
// It's not clear what scale brightness is, or how it is to be applied,
// so we assume 8 bits applied over the given rgb values.
void five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16, uint8_t brightness,
CRGB *out, uint8_t *out_power_5bit) {
uint8_t r8 = 0, g8 = 0, b8 = 0;
// Apply any brightness setting (we assume brightness is 0..255)
if (brightness != 0xff) {
r16 = scale16by8(r16, brightness);
g16 = scale16by8(g16, brightness);
b16 = scale16by8(b16, brightness);
}
// Locate the largest value to set the brightness/scale factor
uint16_t scale = max3(r16, g16, b16);
if (scale == 0) {
*out = CRGB(0, 0, 0);
*out_power_5bit = 0;
return;
} else {
uint32_t scalef;
// Compute 5 bit quantized scale that is at or above the maximum value.
scale = (scale + (2047 - (scale >> 5))) >> 11;
// Adjust the 16 bit values to account for the scale, then round to 8 bits
scalef = bright_scale[scale];
r8 = (r16 * scalef + 0x808000) >> 24;
g8 = (g16 * scalef + 0x808000) >> 24;
b8 = (b16 * scalef + 0x808000) >> 24;
*out = CRGB(r8, g8, b8);
*out_power_5bit = scale;
return;
}
}

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Ensure UTF-8 everywhere (console and Python), including on Windows msys/cygwin
case "$OSTYPE" in
msys*|cygwin*)
if command -v chcp.com >/dev/null 2>&1; then
chcp.com 65001 >NUL 2>NUL || chcp.com 65001 >/dev/null 2>&1
fi
;;
*)
:
;;
esac
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
export PYTHONUTF8=1
export PYTHONIOENCODING=UTF-8
set -x
# cd to the directory of the script
cd "$(dirname "$0")"
rm -rf .venv
rm -rf .build
rm -rf .pio
rm -rf .pio_cache
rm -rf ci/tmp
rm -rf tests/.build
rm -rf .*_cache
rm -rf __pycache__
rm -rf .tools
# JavaScript linting cleanup
rm -rf .js-tools
rm -rf ci/__pycache__
rm -rf ci/.*_cache
# remove any CMakeCache.txt files
find . -name "CMakeCache.txt" -type f -delete
rm -f uv.lock
# Remove generated compile_commands.json
rm -f compile_commands.json

View File

@@ -0,0 +1,134 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
* "Trolling" and excessive complaints without the due benefit of contributing code.
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders via a bug report with the title [CODE OF CONDUCT] as the beginning text.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -0,0 +1,2 @@
COMPONENT_ADD_INCLUDEDIRS := ./src src/platforms/esp/32
COMPONENT_SRCDIRS := ./src src/platforms/esp/32

View File

@@ -0,0 +1,34 @@
* Advanced Color Gradient using online version of FastLED.
* https://wokwi.com/projects/285170662915441160
* LedMapper tool for irregular shapes
* https://github.com/jasoncoon/led-mapper
* list of projects on reddit:
* https://www.reddit.com/r/FastLED/wiki/index/user_examples/
* mesh networked esp32 with mutli wifi connections for redundancy
* https://github.com/Souravgoswami/Arduino-FastLED-Cool-Effects
* FastLED-IR: https://github.com/marcmerlin/FastLED-IR
* https://github.com/marcmerlin/NeoMatrix-FastLED-IR?tab=readme-ov-file
* Tree IR:
* https://www.evilgeniuslabs.org/tree-v2
* Esp32 server for fastled
* https://github.com/jasoncoon/esp32-fastled-webserver
* Strip tease - cool fx for strips
* https://github.com/lpaolini/Striptease?tab=readme-ov-file
* Soulematelights:
* https://editor.soulmatelights.com/gallery
* https://github.com/marcmerlin/FastLED_NeoMatrix_SmartMatrix_LEDMatrix_GFX_Demos/blob/master/LEDMatrix/Table_Mark_Estes/Table_Mark_Estes.ino
* AMAZING processing.js artist
* https://x.com/Hau_kun
* llm-min.txt
* https://github.com/marv1nnnnn/llm-min.txt

View File

@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html>
<head>
<title>FastLED Pure JavaScript Architecture Debug Test</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.debug-section {
margin: 20px 0;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
}
#console-output {
width: 100%;
height: 400px;
border: 1px solid #000;
background-color: #000;
color: #00ff00;
font-family: monospace;
font-size: 12px;
overflow-y: scroll;
padding: 10px;
}
button {
margin: 5px;
padding: 10px 20px;
font-size: 14px;
}
</style>
</head>
<body>
<h1>FastLED Pure JavaScript Architecture Debug Test</h1>
<div class="debug-section">
<h2>Module Loading Status</h2>
<div id="module-status">Loading...</div>
</div>
<div class="debug-section">
<h2>Debug Controls</h2>
<button onclick="testModuleLoading()">Test Module Loading</button>
<button onclick="testCallbackFunctions()">Test Callback Functions</button>
<button onclick="testAsyncController()">Test Async Controller</button>
<button onclick="clearConsole()">Clear Console</button>
</div>
<div class="debug-section">
<h2>Debug Console Output</h2>
<div id="console-output"></div>
</div>
<script type="module">
// Capture console output
const consoleOutput = document.getElementById('console-output');
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
function addToConsole(type, ...args) {
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1);
const message = args.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg, null, 2);
} catch (e) {
return String(arg);
}
}
return String(arg);
}).join(' ');
const line = document.createElement('div');
line.style.color = type === 'error' ? '#ff4444' :
type === 'warn' ? '#ffaa00' : '#00ff00';
line.textContent = `[${timestamp}] ${type.toUpperCase()}: ${message}`;
consoleOutput.appendChild(line);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
console.log = (...args) => {
originalLog(...args);
addToConsole('log', ...args);
};
console.error = (...args) => {
originalError(...args);
addToConsole('error', ...args);
};
console.warn = (...args) => {
originalWarn(...args);
addToConsole('warn', ...args);
};
// Test functions
window.clearConsole = function() {
consoleOutput.innerHTML = '';
};
window.testModuleLoading = async function() {
console.log('=== Testing Module Loading ===');
try {
// Try to import debug logger
const { FASTLED_DEBUG_LOG } = await import('./src/platforms/wasm/compiler/fastled_debug_logger.js');
console.log('✓ Debug logger imported successfully');
FASTLED_DEBUG_LOG('TEST', 'Debug logger working correctly');
// Try to import async controller
const { FastLEDAsyncController } = await import('./src/platforms/wasm/compiler/fastled_async_controller.js');
console.log('✓ Async controller imported successfully');
// Try to import callbacks
await import('./src/platforms/wasm/compiler/fastled_callbacks.js');
console.log('✓ Callbacks imported successfully');
// Try to import events
const { fastLEDEvents } = await import('./src/platforms/wasm/compiler/fastled_events.js');
console.log('✓ Events imported successfully');
document.getElementById('module-status').innerHTML = '✅ All modules loaded successfully';
} catch (error) {
console.error('❌ Module loading failed:', error);
document.getElementById('module-status').innerHTML = '❌ Module loading failed: ' + error.message;
}
};
window.testCallbackFunctions = function() {
console.log('=== Testing Callback Functions ===');
const callbacks = [
'FastLED_onFrame',
'FastLED_processUiUpdates',
'FastLED_onStripUpdate',
'FastLED_onStripAdded',
'FastLED_onUiElementsAdded',
'FastLED_onError'
];
callbacks.forEach(callbackName => {
const callback = globalThis[callbackName];
if (typeof callback === 'function') {
console.log(`${callbackName} is available and is a function`);
} else {
console.error(`${callbackName} is NOT available or not a function (type: ${typeof callback})`);
}
});
};
window.testAsyncController = function() {
console.log('=== Testing Async Controller Creation ===');
// Create a mock WASM module
const mockModule = {
cwrap: function(funcName, returnType, argTypes) {
console.log(`Mock cwrap called for: ${funcName}`);
return function(...args) {
console.log(`Mock function ${funcName} called with args:`, args);
return returnType === 'number' ? 0 : null;
};
},
_malloc: function(size) {
console.log(`Mock _malloc called with size: ${size}`);
return 12345; // Mock pointer
},
_free: function(ptr) {
console.log(`Mock _free called with pointer: ${ptr}`);
},
getValue: function(ptr, type) {
console.log(`Mock getValue called with pointer: ${ptr}, type: ${type}`);
return 0;
},
UTF8ToString: function(ptr, length) {
console.log(`Mock UTF8ToString called with pointer: ${ptr}, length: ${length}`);
return '[]'; // Mock empty JSON array
}
};
try {
import('./src/platforms/wasm/compiler/fastled_async_controller.js').then(({ FastLEDAsyncController }) => {
console.log('Creating FastLEDAsyncController with mock module...');
const controller = new FastLEDAsyncController(mockModule, 60);
console.log('✓ FastLEDAsyncController created successfully');
// Test setup
console.log('Testing controller setup...');
controller.setup().then(() => {
console.log('✓ Controller setup completed');
// Test start
console.log('Testing controller start...');
controller.start();
console.log('✓ Controller started');
// Stop after a few seconds
setTimeout(() => {
controller.stop();
console.log('✓ Controller stopped');
}, 3000);
}).catch(error => {
console.error('❌ Controller setup failed:', error);
});
}).catch(error => {
console.error('❌ Failed to import FastLEDAsyncController:', error);
});
} catch (error) {
console.error('❌ Test async controller failed:', error);
}
};
// Auto-run initial tests
console.log('FastLED Pure JavaScript Architecture Debug Test Starting...');
setTimeout(testModuleLoading, 100);
</script>
</body>
</html>

View File

@@ -0,0 +1,3 @@
#!/bin/bash
uv run dev/dev.py

View File

@@ -0,0 +1,17 @@
#if defined(ESP32)
#include "esp_log.h"
// use gcc intialize constructor
// to set log level to ESP_LOG_VERBOSE
// before setup() is called
__attribute__((constructor))
void on_startup() {
esp_log_level_set("*", ESP_LOG_VERBOSE); // set all components to ERROR level
}
#endif // ESP32
#include "../examples/FestivalStick/FestivalStick.ino"

View File

@@ -0,0 +1,356 @@
# FastLED Examples Agent Guidelines
## 🚨 CRITICAL: .INO FILE CREATION RULES
### ⚠️ THINK BEFORE CREATING .INO FILES ⚠️
**.ino files should be created SPARINGLY and ONLY when truly justified.**
### 🚫 WHEN NOT TO CREATE .INO FILES:
- **Testing minor code changes** - Use existing test files or unit tests
- **Quick API validation** - Use unit tests or modify existing examples
- **Debugging specific functions** - Use test files, not new sketches
- **One-off experiments** - Create temporary test files instead
- **Small feature tests** - Extend existing relevant examples
### ✅ WHEN TO CREATE .INO FILES:
#### 1. **Temporary Testing (.ino)**
**Use Pattern:** `temp_<feature>.ino` or `test_<api>.ino`
```cpp
// temp_json_api.ino - Testing new JSON fetch functionality
// test_networking.ino - Validating network stack changes
```
-**FOR:** Testing new APIs during development
-**FOR:** Quick prototyping and validation
-**DELETE AFTER USE** - These are temporary by design
#### 2. **Significant New Feature Examples**
**Use Pattern:** `examples/<FeatureName>/<FeatureName>.ino`
```cpp
// examples/JsonFetchApi/JsonFetchApi.ino - Comprehensive JSON API example
// examples/NetworkStack/NetworkStack.ino - Major networking features
```
-**FOR:** Large, comprehensive new features
-**FOR:** APIs that warrant dedicated examples
-**FOR:** Features that users will commonly implement
-**PERMANENT** - These become part of the example library
### 📋 CREATION CHECKLIST:
**Before creating ANY .ino file, ask:**
1. **🤔 Is this testing a new API?**
- YES → Create `temp_<name>.ino`, delete after testing
- NO → Consider alternatives
2. **🤔 Is this a significant new feature that users will commonly use?**
- YES → Create `examples/<FeatureName>/<FeatureName>.ino`
- NO → Use existing examples or test files
3. **🤔 Can I modify an existing example instead?**
- YES → Extend existing example rather than creating new
- NO → Proceed with creation
4. **🤔 Is this just for debugging/validation?**
- YES → Use unit tests or temporary test files
- NO → Consider if it meets the "significant feature" criteria
### 🔍 REVIEW CRITERIA:
**For Feature Examples (.ino files that stay):**
-**Demonstrates complete, real-world usage patterns**
-**Covers multiple aspects of the feature comprehensively**
-**Provides educational value for users**
-**Shows best practices and common use cases**
-**Is likely to be referenced by multiple users**
**For Temporary Testing (.ino files that get deleted):**
-**Clearly named as temporary (temp_*, test_*)**
-**Focused on specific API validation**
-**Will be deleted after development cycle**
-**Too complex for unit test framework**
### ❌ EXAMPLES OF WHAT NOT TO CREATE:
- `test_basic_led.ino` - Use existing Blink example
- `debug_colors.ino` - Use existing ColorPalette example
- `quick_brightness.ino` - Use unit tests or modify existing example
- `validate_pins.ino` - Use PinTest example or unit tests
### ✅ EXAMPLES OF JUSTIFIED CREATIONS:
- `temp_new_wifi_api.ino` - Testing major new WiFi functionality (temporary)
- `examples/MachineLearning/MachineLearning.ino` - New ML integration feature (permanent)
- `temp_performance_test.ino` - Validating optimization changes (temporary)
### 🧹 CLEANUP RESPONSIBILITY:
- **Temporary files:** Creator must delete when testing is complete
- **Feature examples:** Must be maintained and updated as API evolves
- **Abandoned files:** Regular cleanup reviews to remove unused examples
**Remember: The examples directory is user-facing documentation. Every .ino file should provide clear value to FastLED users.**
## Code Standards for Examples
### Use fl:: Namespace (Not std::)
**DO NOT use `std::` prefixed functions or headers in examples.** This project provides its own STL-equivalent implementations under the `fl::` namespace.
**Examples of what to avoid and use instead:**
**Headers:**
-`#include <vector>` → ✅ `#include "fl/vector.h"`
-`#include <string>` → ✅ `#include "fl/string.h"`
-`#include <optional>` → ✅ `#include "fl/optional.h"`
-`#include <memory>` → ✅ `#include "fl/scoped_ptr.h"` or `#include "fl/ptr.h"`
**Functions and classes:**
-`std::move()` → ✅ `fl::move()`
-`std::vector` → ✅ `fl::vector`
-`std::string` → ✅ `fl::string`
**Why:** The project maintains its own implementations to ensure compatibility across all supported platforms and to avoid bloating the library with unnecessary STL dependencies.
### Memory Management
**🚨 CRITICAL: Always use proper RAII patterns - smart pointers, moveable objects, or wrapper classes instead of raw pointers for resource management.**
**Resource Management Options:**
-**PREFERRED**: `fl::shared_ptr<T>` for shared ownership (multiple references to same object)
-**PREFERRED**: `fl::unique_ptr<T>` for exclusive ownership (single owner, automatic cleanup)
-**PREFERRED**: Moveable wrapper objects (like `fl::promise<T>`) for safe copying and transferring of unique resources
-**ACCEPTABLE**: `fl::vector<T>` storing objects by value when objects support move/copy semantics
-**AVOID**: `fl::vector<T*>` storing raw pointers - use `fl::vector<fl::shared_ptr<T>>` or `fl::vector<fl::unique_ptr<T>>`
-**AVOID**: Manual `new`/`delete` - use `fl::make_shared<T>()` or `fl::make_unique<T>()`
**Examples:**
```cpp
// ✅ GOOD - Using smart pointers
fl::vector<fl::shared_ptr<HttpClient>> mActiveClients;
auto client = fl::make_shared<HttpClient>();
mActiveClients.push_back(client);
// ✅ GOOD - Using unique_ptr for exclusive ownership
fl::unique_ptr<HttpClient> client = fl::make_unique<HttpClient>();
// ✅ GOOD - Moveable wrapper objects (fl::promise example)
fl::vector<fl::promise<Response>> mActivePromises; // Copyable wrapper around unique future
fl::promise<Response> promise = fetch.get(url).execute();
mActivePromises.push_back(promise); // Safe copy, shared internal state
// ❌ BAD - Raw pointers require manual memory management
fl::vector<Promise*> mActivePromises; // Memory leaks possible
Promise* promise = new Promise(); // Who calls delete?
```
### Debug Printing
**Use `FL_WARN` for debug printing in examples.** This ensures consistent debug output that works in both unit tests and live application testing.
**Usage:**
-`FL_WARN("Debug message: " << message);`
-`FL_WARN("Value: %d", value);`
### No Emoticons or Emojis
**NO emoticons or emoji characters are allowed in C++ source files.** This ensures professional, maintainable code that works correctly across all platforms and development environments.
**Examples of what NOT to do in .ino files:**
```cpp
// ❌ BAD - Emoticons in comments
// 🎯 This function handles user input
// ❌ BAD - Emoticons in log messages
FL_WARN("✅ Operation successful!");
FL_WARN("❌ Error occurred: " << error_msg);
// ❌ BAD - Emoticons in string literals
const char* status = "🔄 Processing...";
```
**Examples of correct alternatives:**
```cpp
// ✅ GOOD - Clear text in comments
// TUTORIAL: This function handles user input
// ✅ GOOD - Text prefixes in log messages
FL_WARN("SUCCESS: Operation completed successfully!");
FL_WARN("ERROR: Failed to process request: " << error_msg);
// ✅ GOOD - Descriptive text in string literals
const char* status = "PROCESSING: Request in progress...";
```
### JSON Usage - Ideal API Patterns
**🎯 PREFERRED: Use the modern `fl::Json` class for all JSON operations.** FastLED provides an ideal JSON API that prioritizes type safety, ergonomics, and crash-proof operation.
**✅ IDIOMATIC JSON USAGE:**
```cpp
// NEW: Clean, safe, idiomatic API
fl::Json json = fl::Json::parse(jsonStr);
int brightness = json["config"]["brightness"] | 128; // Gets value or 128 default
string name = json["device"]["name"] | string("default"); // Type-safe with default
bool enabled = json["features"]["networking"] | false; // Never crashes
// Array operations
if (json["effects"].contains("rainbow")) {
// Safe array checking
}
```
**❌ DISCOURAGED: Verbose legacy API:**
```cpp
// OLD: Verbose, error-prone API (still works, but not recommended)
fl::JsonDocument doc;
fl::string error;
fl::parseJson(jsonStr, &doc, &error);
int brightness = doc["config"]["brightness"].as<int>(); // Can crash if missing
```
**📚 Reference Example:** See `examples/Json/Json.ino` for comprehensive usage patterns and API comparison.
## Example Compilation Commands
### Platform Compilation
```bash
# Compile examples for specific platforms
uv run ci/ci-compile.py uno --examples Blink
uv run ci/ci-compile.py esp32dev --examples Blink
uv run ci/ci-compile.py teensy31 --examples Blink
bash compile uno --examples Blink
```
### WASM Compilation
**🎯 HOW TO COMPILE ANY ARDUINO SKETCH TO WASM:**
**Basic Compilation Commands:**
```bash
# Compile any sketch directory to WASM
uv run ci/wasm_compile.py path/to/your/sketch
# Quick compile test (compile only, no browser)
uv run ci/wasm_compile.py path/to/your/sketch --just-compile
# Compile examples/Blink to WASM
uv run ci/wasm_compile.py examples/Blink --just-compile
# Compile examples/NetTest to WASM (test fetch API)
uv run ci/wasm_compile.py examples/NetTest --just-compile
# Compile examples/DemoReel100 to WASM
uv run ci/wasm_compile.py examples/DemoReel100 --just-compile
```
**Output:** Creates `fastled_js/` folder with:
- `fastled.js` - JavaScript loader
- `fastled.wasm` - WebAssembly binary
- `index.html` - HTML page to run the sketch
**Run in Browser:**
```bash
# Simple HTTP server to test
cd fastled_js
python -m http.server 8000
# Open http://localhost:8000
```
**🚨 REQUIREMENTS:**
- **Docker must be installed and running**
- **Internet connection** (for Docker image download on first run)
- **~1GB RAM** for Docker container during compilation
### WASM Testing Requirements
**🚨 MANDATORY: Always test WASM compilation after platform file changes**
**Platform Testing Commands:**
```bash
# Test WASM platform changes (for platform developers)
uv run ci/wasm_compile.py examples/wasm --just-compile
# Quick compile test for any sketch (compile only, no browser)
uv run ci/wasm_compile.py examples/Blink --just-compile
# Quick compile test for NetTest example
uv run ci/wasm_compile.py examples/NetTest --just-compile
# Quick test without full build
uv run ci/wasm_compile.py examples/wasm --quick
```
**Watch For These Error Patterns:**
- `error: conflicting types for 'function_name'`
- `error: redefinition of 'function_name'`
- `warning: attribute declaration must precede definition`
- `RuntimeError: unreachable` (often async-related)
**MANDATORY RULES:**
- **ALWAYS test WASM compilation** after modifying any WASM platform files
- **USE `uv run ci/wasm_compile.py` for validation**
- **WATCH for unified build conflicts** in compilation output
- **VERIFY async operations work properly** in browser environment
## Compiler Warning Suppression
**ALWAYS use the FastLED compiler control macros from `fl/compiler_control.h` for warning suppression.** This ensures consistent cross-compiler support and proper handling of platform differences.
**Correct Warning Suppression Pattern:**
```cpp
#include "fl/compiler_control.h"
// Suppress specific warning around problematic code
FL_DISABLE_WARNING_PUSH
FL_DISABLE_FORMAT_TRUNCATION // Use specific warning macros
// ... code that triggers warnings ...
FL_DISABLE_WARNING_POP
```
**Available Warning Suppression Macros:**
-`FL_DISABLE_WARNING_PUSH` / `FL_DISABLE_WARNING_POP` - Standard push/pop pattern
-`FL_DISABLE_WARNING(warning_name)` - Generic warning suppression (use sparingly)
-`FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS` - Clang global constructor warnings
-`FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED` - Clang self-assignment warnings
-`FL_DISABLE_FORMAT_TRUNCATION` - GCC format truncation warnings
**What NOT to do:**
-**NEVER use raw `#pragma` directives** - they don't handle compiler differences
-**NEVER write manual `#ifdef __clang__` / `#ifdef __GNUC__` blocks** - use the macros
-**NEVER ignore warnings without suppression** - fix the issue or suppress appropriately
## Exception Handling
**DO NOT use try-catch blocks or C++ exception handling in examples.** FastLED is designed to work on embedded systems like Arduino where exception handling may not be available or desired due to memory and performance constraints.
**Use Error Handling Alternatives:**
-**Return error codes:** `bool function() { return false; }` or custom error enums
-**Optional types:** `fl::optional<T>` for functions that may not return a value
-**Assertions:** `FL_ASSERT(condition)` for debug-time validation
-**Early returns:** `if (!valid) return false;` for error conditions
-**Status objects:** Custom result types that combine success/failure with data
**Examples of proper error handling:**
```cpp
// Good: Using return codes
bool initializeHardware() {
if (!setupPins()) {
FL_WARN("Failed to setup pins");
return false;
}
return true;
}
// Good: Using fl::optional
fl::optional<float> calculateValue(int input) {
if (input < 0) {
return fl::nullopt; // No value, indicates error
}
return fl::make_optional(sqrt(input));
}
// Good: Using early returns
void processData(const uint8_t* data, size_t len) {
if (!data || len == 0) {
FL_WARN("Invalid input data");
return; // Early return on error
}
// Process data...
}
```
## Memory Refresh Rule
**🚨 ALL AGENTS: Read examples/AGENTS.md before concluding example work to refresh memory about .ino file creation rules and example coding standards.**

View File

@@ -0,0 +1,27 @@
// Simple Adafruit Bridge Demo
//
// This example shows how to use the Adafruit_NeoPixel library with FastLED.
// As long as the Adafruit_NeoPixel library is installed (that is #include <Adafruit_NeoPixel.h>
// is present), this example will work. Otherwise you'll get warnings about a missing driver.
#define FASTLED_USE_ADAFRUIT_NEOPIXEL
#include "FastLED.h"
#define DATA_PIN 3
#define NUM_LEDS 10
CRGB leds[NUM_LEDS];
uint8_t hue = 0;
void setup() {
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(50);
}
void loop() {
fill_rainbow(leds, NUM_LEDS, hue, 255/NUM_LEDS);
FastLED.show();
hue++;
delay(50);
}

View File

@@ -0,0 +1,72 @@
/// @file AnalogOutput.ino
/// @brief Demonstrates how to use FastLED color functions even without a "pixel-addressible" smart LED strip.
/// @example AnalogOutput.ino
#include <Arduino.h>
#include <FastLED.h>
#include "./compat.h"
// Example showing how to use FastLED color functions
// even when you're NOT using a "pixel-addressible" smart LED strip.
//
// This example is designed to control an "analog" RGB LED strip
// (or a single RGB LED) being driven by Arduino PWM output pins.
// So this code never calls FastLED.addLEDs() or FastLED.show().
//
// This example illustrates one way you can use just the portions
// of FastLED that you need. In this case, this code uses just the
// fast HSV color conversion code.
//
// In this example, the RGB values are output on three separate
// 'analog' PWM pins, one for red, one for green, and one for blue.
#define REDPIN 5
#define GREENPIN 6
#define BLUEPIN 3
// showAnalogRGB: this is like FastLED.show(), but outputs on
// analog PWM output pins instead of sending data to an intelligent,
// pixel-addressable LED strip.
//
// This function takes the incoming RGB values and outputs the values
// on three analog PWM output pins to the r, g, and b values respectively.
void showAnalogRGB( const CRGB& rgb)
{
analogWrite(REDPIN, rgb.r );
analogWrite(GREENPIN, rgb.g );
analogWrite(BLUEPIN, rgb.b );
}
// colorBars: flashes Red, then Green, then Blue, then Black.
// Helpful for diagnosing if you've mis-wired which is which.
void colorBars()
{
showAnalogRGB( CRGB::Red ); delay(500);
showAnalogRGB( CRGB::Green ); delay(500);
showAnalogRGB( CRGB::Blue ); delay(500);
showAnalogRGB( CRGB::Black ); delay(500);
}
void loop()
{
static uint8_t hue;
hue = hue + 1;
// Use FastLED automatic HSV->RGB conversion
showAnalogRGB( CHSV( hue, 255, 255) );
delay(20);
}
void setup() {
pinMode(REDPIN, OUTPUT);
pinMode(GREENPIN, OUTPUT);
pinMode(BLUEPIN, OUTPUT);
// Flash the "hello" color sequence: R, G, B, black.
colorBars();
}

View File

@@ -0,0 +1,42 @@
#ifdef ARDUINO_ESP32_DEV
#include "fl/compiler_control.h"
#include "platforms/esp/esp_version.h"
#include "driver/ledc.h"
#include "esp32-hal-ledc.h"
// Ancient versions of ESP32 on Arduino (IDF < 4.0) did not have analogWrite() defined.
// IDF 4.4 and 5.0+ both have analogWrite() available, so this polyfill is only needed
// for very old IDF versions. We use a weak symbol so it auto-disables on newer platforms.
#if !ESP_IDF_VERSION_4_OR_HIGHER
FL_LINK_WEAK void analogWrite(uint8_t pin, int value) {
// Setup PWM channel for the pin if not already done
static bool channels_setup[16] = {false}; // ESP32 has 16 PWM channels
static uint8_t channel_counter = 0;
// Find or assign channel for this pin
static uint8_t pin_to_channel[40] = {255}; // ESP32 has up to 40 GPIO pins, 255 = unassigned
if (pin_to_channel[pin] == 255) {
pin_to_channel[pin] = channel_counter++;
if (channel_counter > 15) channel_counter = 0; // Wrap around
}
uint8_t channel = pin_to_channel[pin];
// Setup channel if not already done
if (!channels_setup[channel]) {
ledcSetup(channel, 5000, 8); // 5kHz frequency, 8-bit resolution
ledcAttachPin(pin, channel);
channels_setup[channel] = true;
}
// Write PWM value (0-255 for 8-bit resolution)
ledcWrite(channel, value);
}
#endif // !ESP_IDF_VERSION_4_OR_HIGHER
#endif // ESP32

View File

@@ -0,0 +1,158 @@
/// @file Animartrix.ino
/// @brief Demo of the Animatrix effects
/// @example Animartrix.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
///
/// @author Stefan Petrick
/// @author Zach Vorhies (FastLED adaptation)
///
/*
This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled
Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable
all the UI elements you see below.
OVERVIEW:
This is the famouse Animartrix demo by Stefan Petrick. The effects are generated
using polor polar coordinates. The effects are very complex and powerful.
*/
#define FL_ANIMARTRIX_USES_FAST_MATH 1
/*
Performence notes @64x64:
* ESP32-S3:
* FL_ANIMARTRIX_USES_FAST_MATH 0: 143ms
* FL_ANIMARTRIX_USES_FAST_MATH 1: 90ms
*/
#include "FastLED.h"
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
// DRAW TIME: 7ms
#include <FastLED.h>
#include "fl/json.h"
#include "fl/slice.h"
#include "fx/fx_engine.h"
#include "fx/2d/animartrix.hpp"
#include "fl/ui.h"
using namespace fl;
#define LED_PIN 3
#define BRIGHTNESS 32
#define COLOR_ORDER GRB
#define MATRIX_WIDTH 64
#define MATRIX_HEIGHT 64
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
#define FIRST_ANIMATION POLAR_WAVES
// This is purely use for the web compiler to display the animartrix effects.
// This small led was chosen because otherwise the bloom effect is too strong.
#define LED_DIAMETER 0.15 // .15 cm or 1.5mm
#define POWER_LIMITER_ACTIVE
#define POWER_VOLTS 5
#define POWER_MILLIAMPS 2000
CRGB leds[NUM_LEDS];
XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT);
UITitle title("Animartrix");
UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick");
UISlider brightness("Brightness", BRIGHTNESS, 0, 255);
UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS - 1);
UINumberField colorOrder("Color Order", 0, 0, 5);
UISlider timeSpeed("Time Speed", 1, -10, 10, .1);
Animartrix animartrix(xyMap, FIRST_ANIMATION);
FxEngine fxEngine(NUM_LEDS);
const bool kPowerLimiterActive = false;
void setup_max_power() {
if (kPowerLimiterActive) {
FastLED.setMaxPowerInVoltsAndMilliamps(POWER_VOLTS, POWER_MILLIAMPS); // Set max power to 2 amps
}
}
void setup() {
Serial.begin(115200);
FL_WARN("*** SETUP ***");
auto screen_map = xyMap.toScreenMap();
screen_map.setDiameter(LED_DIAMETER);
FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screen_map);
FastLED.setBrightness(brightness);
setup_max_power();
fxEngine.addFx(animartrix);
colorOrder.onChanged([](int value) {
switch(value) {
case 0: value = RGB; break;
case 1: value = RBG; break;
case 2: value = GRB; break;
case 3: value = GBR; break;
case 4: value = BRG; break;
case 5: value = BGR; break;
}
animartrix.setColorOrder(static_cast<EOrder>(value));
});
}
void loop() {
FL_WARN("*** LOOP ***");
uint32_t start = millis();
FastLED.setBrightness(brightness);
fxEngine.setSpeed(timeSpeed);
static int lastFxIndex = -1;
if (fxIndex.value() != lastFxIndex) {
lastFxIndex = fxIndex;
animartrix.fxSet(fxIndex);
}
fxEngine.draw(millis(), leds);
uint32_t end = millis();
FL_WARN("*** DRAW TIME: " << int(end - start) << "ms");
FastLED.show();
uint32_t end2 = millis();
FL_WARN("*** SHOW + DRAW TIME: " << int(end2 - start) << "ms");
}
#endif // __AVR__

View File

@@ -0,0 +1,38 @@
#include <Arduino.h>
#include <FastLED.h>
#include <lib8tion.h>
#define NUM_LEDS 20
#define STRIP_DATA_PIN 1
#define STRIP_CLOCK_PIN 2
CRGB leds[NUM_LEDS] = {0}; // Software gamma mode.
void setup() {
delay(500); // power-up safety delay
// Two strips of LEDs, one in HD mode, one in software gamma mode.
FastLED.addLeds<APA102, STRIP_DATA_PIN, STRIP_CLOCK_PIN, RGB>(leds, NUM_LEDS);
}
uint8_t wrap_8bit(int i) {
// Modulo % operator here wraps a large "i" so that it is
// always in [0, 255] range when returned. For example, if
// "i" is 256, then this will return 0. If "i" is 257,
// then this will return 1. No matter how big the "i" is, the
// output range will always be [0, 255]
return i % 256;
}
void loop() {
// Draw a linear ramp of brightnesses to showcase the difference between
// the HD and non-HD mode.
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255);
CRGB c(brightness, brightness, brightness); // Just make a shade of white.
leds[i] = c;
}
FastLED.show(); // All LEDs are now displayed.
delay(8); // Wait 8 milliseconds until the next frame.
}

View File

@@ -0,0 +1,88 @@
/// @file Apa102HD.ino
/// @brief Example showing how to use the APA102HD gamma correction.
///
/// In this example we compare two strips of LEDs.
/// One strip is in HD mode, the other is in software gamma mode.
///
/// Each strip is a linear ramp of brightnesses, from 0 to 255.
/// Showcasing all the different brightnesses.
///
/// Why do we love gamma correction? Gamma correction more closely
/// matches how humans see light. Led values are measured in fractions
/// of max power output (1/255, 2/255, etc.), while humans see light
/// in a logarithmic way. Gamma correction converts to this eye friendly
/// curve. Gamma correction wants a LED with a high bit depth. The APA102
/// gives us the standard 3 components (red, green, blue) with 8 bits each, it
/// *also* has a 5 bit brightness component. This gives us a total of 13 bits,
/// which allows us to achieve a higher dynamic range. This means deeper fades.
///
/// Example:
/// CRGB leds[NUM_LEDS] = {0};
/// void setup() {
/// FastLED.addLeds<
/// APA102HD, // <--- This selects HD mode.
/// STRIP_0_DATA_PIN,
/// STRIP_0_CLOCK_PIN,
/// RGB
/// >(leds, NUM_LEDS);
/// }
#include <Arduino.h>
#include <FastLED.h>
#include <lib8tion.h>
#define NUM_LEDS 20
// uint8_t DATA_PIN, uint8_t CLOCK_PIN,
#define STRIP_0_DATA_PIN 1
#define STRIP_0_CLOCK_PIN 2
#define STRIP_1_DATA_PIN 3
#define STRIP_1_CLOCK_PIN 4
CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma.
CRGB leds[NUM_LEDS] = {0}; // Software gamma mode.
// This is the regular gamma correction function that we used to have
// to do. It's used here to showcase the difference between APA102HD
// mode which does the gamma correction for you.
CRGB software_gamma(const CRGB& in) {
CRGB out;
// dim8_raw are the old gamma correction functions.
out.r = dim8_raw(in.r);
out.g = dim8_raw(in.g);
out.b = dim8_raw(in.b);
return out;
}
void setup() {
delay(500); // power-up safety delay
// Two strips of LEDs, one in HD mode, one in software gamma mode.
FastLED.addLeds<APA102HD, STRIP_0_DATA_PIN, STRIP_0_CLOCK_PIN, RGB>(leds_hd, NUM_LEDS);
FastLED.addLeds<APA102, STRIP_1_DATA_PIN, STRIP_1_CLOCK_PIN, RGB>(leds, NUM_LEDS);
}
uint8_t wrap_8bit(int i) {
// Module % operator here wraps a large "i" so that it is
// always in [0, 255] range when returned. For example, if
// "i" is 256, then this will return 0. If "i" is 257
// then this will return 1. No matter how big the "i" is, the
// output range will always be [0, 255]
return i % 256;
}
void loop() {
// Draw a a linear ramp of brightnesses to showcase the difference between
// the HD and non-HD mode.
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255);
CRGB c(brightness, brightness, brightness); // Just make a shade of white.
leds_hd[i] = c; // The APA102HD leds do their own gamma correction.
CRGB c_gamma_corrected = software_gamma(c);
leds[i] = c_gamma_corrected; // Set the software gamma corrected
// values to the other strip.
}
FastLED.show(); // All leds are now written out.
delay(8); // Wait 8 milliseconds until the next frame.
}

View File

@@ -0,0 +1,50 @@
/// @file Apa102HD.ino
/// @brief Example showing how to use the APA102HD gamma correction with user override.
#define FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE
#include <Arduino.h>
#include <FastLED.h>
#include <lib8tion.h>
#define NUM_LEDS 20
// uint8_t DATA_PIN, uint8_t CLOCK_PIN,
#define STRIP_0_DATA_PIN 1
#define STRIP_0_CLOCK_PIN 2
#define STRIP_1_DATA_PIN 3
#define STRIP_1_CLOCK_PIN 4
void fl::five_bit_hd_gamma_bitshift(CRGB colors,
CRGB scale,
uint8_t global_brightness,
CRGB* out_colors,
uint8_t *out_power_5bit) {
// all 0 values for output
*out_colors = CRGB(0, 0, 0);
*out_power_5bit = 0;
Serial.println("Override function called");
}
CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma.
void setup() {
delay(500); // power-up safety delay
// Two strips of LEDs, one in HD mode, one in software gamma mode.
FastLED.addLeds<APA102HD, STRIP_0_DATA_PIN, STRIP_0_CLOCK_PIN, RGB>(
leds_hd, NUM_LEDS);
}
void loop() {
// Draw a a linear ramp of brightnesses to showcase the difference between
// the HD and non-HD mode.
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255);
CRGB c(brightness, brightness,
brightness); // Just make a shade of white.
leds_hd[i] = c; // The APA102HD leds do their own gamma correction.
}
FastLED.show(); // All leds are now written out.
delay(8); // Wait 8 milliseconds until the next frame.
}

View File

@@ -0,0 +1,35 @@
// @file examples/TaskExample/TaskExample.ino
// @brief Example showing how to use the fl::task API
#include "FastLED.h"
#include "fl/task.h"
#include "fl/async.h"
#define NUM_LEDS 60
#define DATA_PIN 5
CRGB leds[NUM_LEDS];
CRGB current_color = CRGB::Black;
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(16);
// Create a task that runs every 100ms to change color
auto colorPicker = fl::task::every_ms(100, FL_TRACE)
.then([] {
current_color = CHSV(random8(), 255, 255);
});
// Create a task that runs at 60fps to update the LEDs
auto displayTask = fl::task::at_framerate(60, FL_TRACE)
.then([] {
fill_solid(leds, NUM_LEDS, current_color);
FastLED.show();
});
}
void loop() {
// Yield to allow other operations to run
fl::async_run();
}

View File

@@ -0,0 +1,19 @@
#include <Arduino.h>
#include "fl/sketch_macros.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "./Async.h"
#else
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println("Not enough memory");
delay(1000);
}
#endif

View File

@@ -0,0 +1,39 @@
/// @file Audio.ino
/// @brief Audio visualization example with XY mapping
/// @example Audio.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
/*
This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled
Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable
all the UI elements you see below.
*/
// #define SIMPLE_EXAMPLE
#include <FastLED.h>
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#ifdef SIMPLE_EXAMPLE
#include "simple/simple.h"
#else
#include "advanced/advanced.h"
#endif
#endif

View File

@@ -0,0 +1,148 @@
# Audio Reactive Visualizations
This example demonstrates various audio-reactive visualization modes using FastLED. It processes real-time audio input and creates stunning visual effects synchronized to music.
## Features
### Visualization Modes
1. **Spectrum Bars** - Classic frequency spectrum analyzer with vertical bars
2. **Radial Spectrum** - Circular frequency visualization radiating from center
3. **Waveform** - Real-time audio waveform display
4. **VU Meter** - Traditional volume unit meter with RMS and peak levels
5. **Matrix Rain** - Audio-reactive digital rain effect
6. **Fire Effect** - Flame simulation that responds to audio intensity
7. **Plasma Wave** - Animated plasma patterns modulated by audio
### Audio Processing
- **Real-time FFT Analysis** - Frequency spectrum analysis
- **Beat Detection** - Automatic beat detection with adjustable sensitivity
- **Auto Gain Control** - Automatically adjusts to varying audio levels
- **Noise Floor Filtering** - Removes background noise
- **Peak Detection** - Tracks audio peaks with smoothing
### Visual Controls
- **7 Color Palettes** - Rainbow, Heat, Ocean, Forest, Party, Lava, Cloud
- **Brightness Control** - Adjustable LED brightness
- **Fade Speed** - Control trail/persistence effects
- **Mirror Mode** - Create symmetrical displays
- **Beat Flash** - Visual feedback on beat detection
## Hardware Requirements
- **Controller**: ESP32, Teensy, or other platform with sufficient memory
- **LED Matrix**: 100x100 pixels (10,000 LEDs total)
- **Memory**: Requires `SKETCH_HAS_LOTS_OF_MEMORY` (not suitable for Arduino UNO)
- **Audio Input**: Microphone or line-in audio source
## Wiring
```
LED Matrix:
- Data Pin: GPIO 3 (configurable via LED_PIN)
- Power: 5V (ensure adequate power supply for 10,000 LEDs!)
- Ground: Common ground with controller
Audio Input:
- Follow your platform's audio input configuration
```
## Configuration
### Display Settings
```cpp
#define WIDTH 100 // Matrix width
#define HEIGHT 100 // Matrix height
#define LED_PIN 3 // Data pin for LEDs
#define LED_TYPE WS2812B // LED chipset
#define COLOR_ORDER GRB // Color order
```
### Audio Settings
```cpp
#define SAMPLE_RATE 44100 // Audio sample rate
#define FFT_SIZE 512 // FFT size for frequency analysis
```
## UI Controls
### Master Controls
- **Enable Audio** - Toggle audio processing on/off
- **Visualization Mode** - Select from 7 different modes
### Audio Controls
- **Audio Gain** - Manual gain adjustment (0.1 - 5.0)
- **Noise Floor** - Background noise threshold (0.0 - 1.0)
- **Auto Gain** - Enable automatic gain control
### Visual Controls
- **Brightness** - LED brightness (0 - 255)
- **Fade Speed** - Trail effect speed (0 - 255)
- **Color Palette** - Choose color scheme
- **Mirror Mode** - Enable symmetrical display
### Beat Detection
- **Beat Detection** - Enable/disable beat detection
- **Beat Sensitivity** - Adjust detection threshold (0.5 - 3.0)
- **Beat Flash** - Enable visual flash on beats
## Usage
1. Upload the sketch to your controller
2. Connect your LED matrix
3. Provide audio input (microphone or line-in)
4. Use the web UI to control visualizations
5. Select different modes and adjust parameters in real-time
## Performance Tips
- This example requires significant processing power
- Reduce matrix size if experiencing lag
- Disable beat detection for lower CPU usage
- Use simpler visualization modes on slower processors
## Customization
### Adding New Visualizations
1. Add your mode name to the `visualMode` dropdown
2. Create a new `drawYourMode()` function
3. Add a case in the main switch statement
4. Implement your visualization logic
### Modifying Color Palettes
Edit the `getCurrentPalette()` function to add custom palettes:
```cpp
case 7: return YourCustomPalette_p;
```
### Adjusting Matrix Size
For different matrix sizes, modify:
```cpp
#define WIDTH your_width
#define HEIGHT your_height
```
## Memory Usage
This example uses approximately:
- 30KB for LED buffer (100x100 RGB)
- 2KB for FFT data
- 1KB for audio buffers
- Additional memory for effect buffers
## Troubleshooting
- **No visualization**: Check audio input and "Enable Audio" setting
- **Choppy animation**: Reduce matrix size or disable some features
- **No beat detection**: Increase beat sensitivity or check audio levels
- **Dim display**: Increase brightness or check power supply
- **Compilation error**: Ensure platform has sufficient memory
## Credits
This example demonstrates the audio processing capabilities of FastLED, including FFT analysis, beat detection, and various visualization techniques suitable for LED art installations, music visualizers, and interactive displays.

View File

@@ -0,0 +1,562 @@
/// @file AudioReactive.ino
/// @brief Audio reactive visualization with multiple modes
/// @example AudioReactive.ino
#include <Arduino.h>
#include <FastLED.h>
#include "fl/ui.h"
#include "fl/audio.h"
#include "fl/fft.h"
#include "fl/xymap.h"
#include "fl/math.h"
#include "fl/math_macros.h"
#include "fl/compiler_control.h"
// This is used by fastled because we have extremely strict compiler settings.
// Stock Arduino/Platformio does not need these.
FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING(float-conversion)
FL_DISABLE_WARNING(sign-conversion)
using namespace fl;
// Display configuration
// For WebAssembly, use a smaller display to avoid memory issues
#ifdef __EMSCRIPTEN__
#define WIDTH 32
#define HEIGHT 32
#else
#define WIDTH 64
#define HEIGHT 64
#endif
#define NUM_LEDS (WIDTH * HEIGHT)
#define LED_PIN 3
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
// Audio configuration
#define SAMPLE_RATE 44100
#define FFT_SIZE 512
// UI Elements
UITitle title("Audio Reactive Visualizations");
UIDescription description("Real-time audio visualizations with beat detection and multiple modes");
// Master controls
UICheckbox enableAudio("Enable Audio", true);
UIDropdown visualMode("Visualization Mode",
{"Spectrum Bars", "Radial Spectrum", "Waveform", "VU Meter", "Matrix Rain", "Fire Effect", "Plasma Wave"});
// Audio controls
UISlider audioGain("Audio Gain", 1.0f, 0.1f, 5.0f, 0.1f);
UISlider noiseFloor("Noise Floor", 0.1f, 0.0f, 1.0f, 0.01f);
UICheckbox autoGain("Auto Gain", true);
// Visual controls
UISlider brightness("Brightness", 128, 0, 255, 1);
UISlider fadeSpeed("Fade Speed", 20, 0, 255, 1);
UIDropdown colorPalette("Color Palette",
{"Rainbow", "Heat", "Ocean", "Forest", "Party", "Lava", "Cloud"});
UICheckbox mirrorMode("Mirror Mode", false);
// Beat detection
UICheckbox beatDetect("Beat Detection", true);
UISlider beatSensitivity("Beat Sensitivity", 1.5f, 0.5f, 3.0f, 0.1f);
UICheckbox beatFlash("Beat Flash", true);
// Audio input
UIAudio audio("Audio Input");
// Global variables
CRGB leds[NUM_LEDS];
XYMap xyMap(WIDTH, HEIGHT, false);
SoundLevelMeter soundMeter(0.0, 0.0);
// Audio processing variables - keep these smaller for WebAssembly
static const int NUM_BANDS = 16; // Reduced from 32
float fftSmooth[NUM_BANDS] = {0};
float beatHistory[20] = {0}; // Reduced from 43
int beatHistoryIndex = 0;
float beatAverage = 0;
float beatVariance = 0;
uint32_t lastBeatTime = 0;
bool isBeat = false;
float autoGainValue = 1.0f;
float peakLevel = 0;
// Visual effect variables
uint8_t hue = 0;
// Remove large static arrays for WebAssembly
#ifndef __EMSCRIPTEN__
float plasma[WIDTH][HEIGHT] = {{0}};
uint8_t fireBuffer[WIDTH][HEIGHT] = {{0}};
#endif
// Get current color palette
CRGBPalette16 getCurrentPalette() {
switch(colorPalette.as_int()) {
case 0: return CRGBPalette16(RainbowColors_p);
case 1: return CRGBPalette16(HeatColors_p);
case 2: return CRGBPalette16(OceanColors_p);
case 3: return CRGBPalette16(ForestColors_p);
case 4: return CRGBPalette16(PartyColors_p);
case 5: return CRGBPalette16(LavaColors_p);
case 6: return CRGBPalette16(CloudColors_p);
default: return CRGBPalette16(RainbowColors_p);
}
}
// Beat detection algorithm
bool detectBeat(float energy) {
beatHistory[beatHistoryIndex] = energy;
beatHistoryIndex = (beatHistoryIndex + 1) % 20;
// Calculate average
beatAverage = 0;
for (int i = 0; i < 20; i++) {
beatAverage += beatHistory[i];
}
beatAverage /= 20.0f;
// Calculate variance
beatVariance = 0;
for (int i = 0; i < 20; i++) {
float diff = beatHistory[i] - beatAverage;
beatVariance += diff * diff;
}
beatVariance /= 20.0f;
// Detect beat
float threshold = beatAverage + (beatSensitivity.value() * sqrt(beatVariance));
uint32_t currentTime = millis();
if (energy > threshold && (currentTime - lastBeatTime) > 80) {
lastBeatTime = currentTime;
return true;
}
return false;
}
// Update auto gain
void updateAutoGain(float level) {
if (!autoGain) {
autoGainValue = 1.0f;
return;
}
static float targetLevel = 0.7f;
static float avgLevel = 0.0f;
avgLevel = avgLevel * 0.95f + level * 0.05f;
if (avgLevel > 0.01f) {
float gainAdjust = targetLevel / avgLevel;
gainAdjust = fl::clamp(gainAdjust, 0.5f, 2.0f);
autoGainValue = autoGainValue * 0.9f + gainAdjust * 0.1f;
}
}
// Clear display
void clearDisplay() {
if (fadeSpeed.as_int() == 0) {
fill_solid(leds, NUM_LEDS, CRGB::Black);
} else {
fadeToBlackBy(leds, NUM_LEDS, fadeSpeed.as_int());
}
}
// Visualization: Spectrum Bars
void drawSpectrumBars(FFTBins* fft, float /* peak */) {
clearDisplay();
CRGBPalette16 palette = getCurrentPalette();
int barWidth = WIDTH / NUM_BANDS;
for (size_t band = 0; band < NUM_BANDS && band < fft->bins_db.size(); band++) {
float magnitude = fft->bins_db[band];
// Apply noise floor
magnitude = magnitude / 100.0f; // Normalize from dB
magnitude = MAX(0.0f, magnitude - noiseFloor.value());
// Smooth the FFT
fftSmooth[band] = fftSmooth[band] * 0.8f + magnitude * 0.2f;
magnitude = fftSmooth[band];
// Apply gain
magnitude *= audioGain.value() * autoGainValue;
magnitude = fl::clamp(magnitude, 0.0f, 1.0f);
int barHeight = magnitude * HEIGHT;
int xStart = band * barWidth;
for (int x = 0; x < barWidth - 1; x++) {
for (int y = 0; y < barHeight; y++) {
uint8_t colorIndex = fl::map_range<float, uint8_t>(
float(y) / HEIGHT, 0, 1, 0, 255
);
CRGB color = ColorFromPalette(palette, colorIndex + hue);
int ledIndex = xyMap(xStart + x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = color;
}
if (mirrorMode) {
int mirrorIndex = xyMap(WIDTH - 1 - (xStart + x), y);
if (mirrorIndex >= 0 && mirrorIndex < NUM_LEDS) {
leds[mirrorIndex] = color;
}
}
}
}
}
}
// Visualization: Radial Spectrum
void drawRadialSpectrum(FFTBins* fft, float /* peak */) {
clearDisplay();
CRGBPalette16 palette = getCurrentPalette();
int centerX = WIDTH / 2;
int centerY = HEIGHT / 2;
for (size_t angle = 0; angle < 360; angle += 6) { // Reduced resolution
size_t band = (angle / 6) % NUM_BANDS;
if (band >= fft->bins_db.size()) continue;
float magnitude = fft->bins_db[band] / 100.0f;
magnitude = MAX(0.0f, magnitude - noiseFloor.value());
magnitude *= audioGain.value() * autoGainValue;
magnitude = fl::clamp(magnitude, 0.0f, 1.0f);
int radius = magnitude * (MIN(WIDTH, HEIGHT) / 2);
for (int r = 0; r < radius; r++) {
int x = centerX + (r * cosf(angle * PI / 180.0f));
int y = centerY + (r * sinf(angle * PI / 180.0f));
if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
uint8_t colorIndex = fl::map_range<int, uint8_t>(r, 0, radius, 255, 0);
int ledIndex = xyMap(x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = ColorFromPalette(palette, colorIndex + hue);
}
}
}
}
}
// Visualization: Logarithmic Waveform (prevents saturation)
void drawWaveform(const Slice<const int16_t>& pcm, float /* peak */) {
clearDisplay();
CRGBPalette16 palette = getCurrentPalette();
int samplesPerPixel = pcm.size() / WIDTH;
int centerY = HEIGHT / 2;
for (size_t x = 0; x < WIDTH; x++) {
size_t sampleIndex = x * samplesPerPixel;
if (sampleIndex >= pcm.size()) break;
// Get the raw sample value
float sample = float(pcm[sampleIndex]) / 32768.0f; // Normalize to -1.0 to 1.0
// Apply logarithmic scaling to prevent saturation
float absSample = fabsf(sample);
float logAmplitude = 0.0f;
if (absSample > 0.001f) { // Avoid log(0)
// Logarithmic compression: log10(1 + gain * sample)
float scaledSample = absSample * audioGain.value() * autoGainValue;
logAmplitude = log10f(1.0f + scaledSample * 9.0f) / log10f(10.0f); // Normalize to 0-1
}
// Apply smooth sensitivity curve
logAmplitude = powf(logAmplitude, 0.7f); // Gamma correction for better visual response
// Calculate amplitude in pixels
int amplitude = int(logAmplitude * (HEIGHT / 2));
amplitude = fl::clamp(amplitude, 0, HEIGHT / 2);
// Preserve the sign for proper waveform display
if (sample < 0) amplitude = -amplitude;
// Color mapping based on amplitude intensity
uint8_t colorIndex = fl::map_range<int, uint8_t>(abs(amplitude), 0, HEIGHT/2, 40, 255);
CRGB color = ColorFromPalette(palette, colorIndex + hue);
// Apply brightness scaling for low amplitudes
if (abs(amplitude) < HEIGHT / 4) {
color.fadeToBlackBy(128 - (abs(amplitude) * 512 / HEIGHT));
}
// Draw vertical line from center
if (amplitude == 0) {
// Draw center point for zero amplitude
int ledIndex = xyMap(x, centerY);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = color.fadeToBlackBy(200);
}
} else {
// Draw line from center to amplitude
int startY = (amplitude > 0) ? centerY : centerY + amplitude;
int endY = (amplitude > 0) ? centerY + amplitude : centerY;
for (int y = startY; y <= endY; y++) {
if (y >= 0 && y < HEIGHT) {
int ledIndex = xyMap(x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
// Fade edges for smoother appearance
CRGB pixelColor = color;
if (y == startY || y == endY) {
pixelColor.fadeToBlackBy(100);
}
leds[ledIndex] = pixelColor;
}
}
}
}
}
}
// Visualization: VU Meter
void drawVUMeter(float rms, float peak) {
clearDisplay();
CRGBPalette16 palette = getCurrentPalette();
// RMS level bar
int rmsWidth = rms * WIDTH * audioGain.value() * autoGainValue;
rmsWidth = MIN(rmsWidth, WIDTH);
for (int x = 0; x < rmsWidth; x++) {
for (int y = HEIGHT/3; y < 2*HEIGHT/3; y++) {
uint8_t colorIndex = fl::map_range<int, uint8_t>(x, 0, WIDTH, 0, 255);
int ledIndex = xyMap(x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = ColorFromPalette(palette, colorIndex);
}
}
}
// Peak indicator
int peakX = peak * WIDTH * audioGain.value() * autoGainValue;
peakX = MIN(peakX, WIDTH - 1);
for (int y = HEIGHT/4; y < 3*HEIGHT/4; y++) {
int ledIndex = xyMap(peakX, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = CRGB::White;
}
}
// Beat indicator
if (isBeat && beatFlash) {
for (int x = 0; x < WIDTH; x++) {
int ledIndex1 = xyMap(x, 0);
int ledIndex2 = xyMap(x, HEIGHT - 1);
if (ledIndex1 >= 0 && ledIndex1 < NUM_LEDS) leds[ledIndex1] = CRGB::White;
if (ledIndex2 >= 0 && ledIndex2 < NUM_LEDS) leds[ledIndex2] = CRGB::White;
}
}
}
// Visualization: Matrix Rain
void drawMatrixRain(float peak) {
// Shift everything down
for (int x = 0; x < WIDTH; x++) {
for (int y = HEIGHT - 1; y > 0; y--) {
int currentIndex = xyMap(x, y);
int aboveIndex = xyMap(x, y - 1);
if (currentIndex >= 0 && currentIndex < NUM_LEDS &&
aboveIndex >= 0 && aboveIndex < NUM_LEDS) {
leds[currentIndex] = leds[aboveIndex];
leds[currentIndex].fadeToBlackBy(40);
}
}
}
// Add new drops based on audio
int numDrops = peak * WIDTH * audioGain.value() * autoGainValue;
for (int i = 0; i < numDrops; i++) {
int x = random(WIDTH);
int ledIndex = xyMap(x, 0);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = CHSV(96, 255, 255); // Green
}
}
}
// Visualization: Fire Effect (simplified for WebAssembly)
void drawFireEffect(float peak) {
// Simple fire effect without buffer
clearDisplay();
// Add heat at bottom based on audio
int heat = 100 + (peak * 155 * audioGain.value() * autoGainValue);
heat = MIN(heat, 255);
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
// Simple gradient from bottom to top
int heatLevel = heat * (HEIGHT - y) / HEIGHT;
heatLevel = heatLevel * random(80, 120) / 100; // Add randomness
heatLevel = MIN(heatLevel, 255);
int ledIndex = xyMap(x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = HeatColor(heatLevel);
}
}
}
}
// Visualization: Plasma Wave
void drawPlasmaWave(float peak) {
static float time = 0;
time += 0.05f + (peak * 0.2f);
CRGBPalette16 palette = getCurrentPalette();
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
float value = sinf(x * 0.1f + time) +
sinf(y * 0.1f - time) +
sinf((x + y) * 0.1f + time) +
sinf(sqrtf(x * x + y * y) * 0.1f - time);
value = (value + 4) / 8; // Normalize to 0-1
value *= audioGain.value() * autoGainValue;
uint8_t colorIndex = value * 255;
int ledIndex = xyMap(x, y);
if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
leds[ledIndex] = ColorFromPalette(palette, colorIndex + hue);
}
}
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Audio Reactive Visualizations");
Serial.println("Initializing...");
Serial.print("Display size: ");
Serial.print(WIDTH);
Serial.print("x");
Serial.println(HEIGHT);
// Initialize LEDs
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(brightness.as_int());
FastLED.clear();
FastLED.show();
// Set up UI callbacks
brightness.onChanged([](float value) {
FastLED.setBrightness(value);
});
Serial.println("Setup complete!");
}
void loop() {
// Check if audio is enabled
if (!enableAudio) {
// Show a simple test pattern
fill_rainbow(leds, NUM_LEDS, hue++, 7);
FastLED.show();
delay(20);
return;
}
// Process only one audio sample per frame to avoid accumulation
AudioSample sample = audio.next();
if (sample.isValid()) {
// Update sound meter
soundMeter.processBlock(sample.pcm());
// Get audio levels
float rms = sample.rms() / 32768.0f;
// Calculate peak
int32_t maxSample = 0;
for (size_t i = 0; i < sample.pcm().size(); i++) {
int32_t absSample = fabsf(sample.pcm()[i]);
if (absSample > maxSample) {
maxSample = absSample;
}
}
float peak = float(maxSample) / 32768.0f;
peakLevel = peakLevel * 0.9f + peak * 0.1f; // Smooth peak
// Update auto gain
updateAutoGain(rms);
// Beat detection
if (beatDetect) {
isBeat = detectBeat(peak);
}
// Get FFT data - create local FFTBins to avoid accumulation
FFTBins fftBins(NUM_BANDS);
sample.fft(&fftBins);
// Update color animation
hue += 1;
// Apply beat flash
if (isBeat && beatFlash) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i].fadeLightBy(-50); // Make brighter
}
}
// Draw selected visualization
switch (visualMode.as_int()) {
case 0: // Spectrum Bars
drawSpectrumBars(&fftBins, peakLevel);
break;
case 1: // Radial Spectrum
drawRadialSpectrum(&fftBins, peakLevel);
break;
case 2: // Waveform
drawWaveform(sample.pcm(), peakLevel);
break;
case 3: // VU Meter
drawVUMeter(rms, peakLevel);
break;
case 4: // Matrix Rain
drawMatrixRain(peakLevel);
break;
case 5: // Fire Effect
drawFireEffect(peakLevel);
break;
case 6: // Plasma Wave
drawPlasmaWave(peakLevel);
break;
}
}
FastLED.show();
// Add a small delay to prevent tight loops in WebAssembly
#ifdef __EMSCRIPTEN__
delay(1);
#endif
}
FL_DISABLE_WARNING_POP

View File

@@ -0,0 +1,157 @@
# Audio Reactive Visualization Example
This example demonstrates advanced audio reactive visualization capabilities using FastLED. It processes real-time audio input and creates stunning visual effects synchronized to music.
## Features
### Visualization Modes
1. **Spectrum Analyzer** - Classic frequency spectrum display with customizable colors
2. **Waveform** - Real-time audio waveform visualization
3. **VU Meter** - Traditional volume unit meter with RMS and peak indicators
4. **Spectrogram** - Scrolling frequency analysis over time
5. **Combined** - Split-screen showing both spectrum and waveform
6. **Reactive Patterns** - Dynamic patterns that respond to audio energy and beats
### Audio Processing
- **Real-time FFT** - Fast Fourier Transform for frequency analysis
- **Beat Detection** - Automatic beat detection with adjustable sensitivity
- **Auto Gain Control (AGC)** - Automatically adjusts to varying audio levels
- **Noise Floor Filtering** - Removes background noise for cleaner visuals
- **Attack/Decay/Sustain** - Professional audio envelope controls
### Visual Controls
- **Multiple Color Palettes** - Heat, Rainbow, Ocean, Forest, Lava, Cloud, Party
- **Mirror Mode** - Creates symmetrical displays
- **Brightness Control** - Adjustable LED brightness
- **Fade Effects** - Smooth transitions with adjustable fade time
- **Color Animation** - Animated color cycling with speed control
- **Smoothing** - Optional smoothing for less jittery displays
### Advanced Features
- **Frequency Band Analysis** - 8-band frequency analyzer for detailed audio analysis
- **FFT Smoothing** - Temporal smoothing of frequency data
- **Logarithmic Scale** - Optional log scale for frequency display
- **Freeze Frame** - Pause the visualization at any moment
- **Frame Advance** - Step through frozen frames
## UI Controls
### Main Controls
- **Enable Audio Reactive Mode** - Master on/off switch for audio processing
- **Visualization Mode** - Dropdown to select visualization type
### Audio Processing Group
- **Fade Time** - How quickly levels decay (0-4 seconds)
- **Attack Time** - How quickly levels rise (0-4 seconds)
- **Output Smoothing** - Final output smoothing (0-2 seconds)
- **Audio Gain** - Manual gain adjustment (0.1-5.0)
- **Noise Floor** - Background noise threshold (-80 to -20 dB)
### Visual Controls Group
- **Fade to Black** - Trail/persistence effect (0-50)
- **Brightness** - LED brightness (0-255)
- **Color Speed** - Animation speed (0.1-5.0)
- **Color Palette** - Choose from 7 palettes
- **Mirror Mode** - Enable symmetrical display
- **Smoothing** - Enable temporal smoothing
### FFT Controls Group
- **Min Frequency** - Lower frequency bound (20-1000 Hz)
- **Max Frequency** - Upper frequency bound (1000-20000 Hz)
- **Logarithmic Scale** - Use log scale for frequency
- **FFT Smoothing** - Smoothing factor (0-0.95)
### Advanced Controls Group
- **Freeze Frame** - Pause visualization
- **Advance Frame** - Step forward when frozen
- **Beat Detection** - Enable beat detection
- **Beat Sensitivity** - Beat detection threshold (0.5-3.0)
- **Auto Gain Control** - Enable automatic gain adjustment
## Hardware Setup
### LED Configuration
- Default: 128x128 LED matrix (16,384 LEDs)
- Downscaled to 64x64 for output (4,096 LEDs)
- Data pin: GPIO 3 (configurable)
- LED type: WS2812B (Neopixel)
### Audio Input
The example uses the FastLED audio system which can accept input from:
- Microphone (real-time audio capture)
- Audio file playback
- System audio (on supported platforms)
## Usage
1. **Basic Operation**
- Upload the sketch to your controller
- Connect your LED matrix
- Provide audio input
- Use the web UI to control visualization
2. **Optimizing for Your Setup**
- Adjust the noise floor if visualization is too sensitive/insensitive
- Use AGC for varying audio levels
- Tune beat sensitivity for your music style
- Experiment with different color palettes and speeds
3. **Performance Tips**
- Reduce matrix size for slower controllers
- Disable smoothing for more responsive display
- Use simpler visualization modes for lower CPU usage
## Code Structure
### Main Components
1. **Audio Processing Pipeline**
```cpp
AudioSample → FFT → Band Analysis → Beat Detection → Visualization
```
2. **Visualization Functions**
- `drawSpectrumAnalyzer()` - Frequency spectrum bars
- `drawWaveform()` - Audio waveform display
- `drawVUMeter()` - Volume meter visualization
- `drawSpectrogram()` - Time-frequency plot
- `drawReactivePatterns()` - Beat-reactive patterns
3. **Audio Analysis Classes**
- `MaxFadeTracker` - Smooth peak tracking with attack/decay
- `BeatDetector` - Energy-based beat detection
- `FrequencyBandAnalyzer` - 8-band frequency analysis
## Customization
### Adding New Visualizations
1. Create a new draw function
2. Add it to the visualization mode dropdown
3. Add a case in the main switch statement
### Modifying Color Palettes
Edit the `getCurrentPalette()` function to add custom palettes.
### Adjusting Frequency Bands
Modify the `FrequencyBandAnalyzer` constructor to change band boundaries.
## Troubleshooting
- **No visualization**: Check audio input and ensure "Enable Audio Reactive Mode" is on
- **Too dim/bright**: Adjust brightness control
- **Choppy animation**: Increase smoothing or reduce matrix size
- **No beat detection**: Adjust beat sensitivity or check audio levels
- **Visualization too sensitive**: Increase noise floor value
## Memory Requirements
This example requires significant memory for:
- Framebuffer: 128×128×3 = 49,152 bytes
- LED buffer: 64×64×3 = 12,288 bytes
- Audio buffers and FFT data
Platforms with limited memory may need to reduce the matrix size.

View File

@@ -0,0 +1,64 @@
#pragma once
#include "fl/time_alpha.h"
#include "fl/math_macros.h"
/// Tracks a smoothed peak with attack, decay, and output-inertia time-constants.
class MaxFadeTracker {
public:
/// @param attackTimeSec τ₁: how quickly to rise toward a new peak.
/// @param decayTimeSec τ₂: how quickly to decay to 1/e of value.
/// @param outputTimeSec τ₃: how quickly the returned value follows currentLevel_.
/// @param sampleRate audio sample rate (e.g. 44100 or 48000).
MaxFadeTracker(float attackTimeSec,
float decayTimeSec,
float outputTimeSec,
float sampleRate)
: attackRate_(1.0f / attackTimeSec)
, decayRate_(1.0f / decayTimeSec)
, outputRate_(1.0f / outputTimeSec)
, sampleRate_(sampleRate)
, currentLevel_(0.0f)
, smoothedOutput_(0.0f)
{}
void setAttackTime(float t){ attackRate_ = 1.0f/t; }
void setDecayTime (float t){ decayRate_ = 1.0f/t; }
void setOutputTime(float t){ outputRate_ = 1.0f/t; }
/// Process one 512-sample block; returns [0…1] with inertia.
float operator()(const int16_t* samples, size_t length) {
assert(length == 512);
// 1) block peak
float peak = 0.0f;
for (size_t i = 0; i < length; ++i) {
float v = ABS(samples[i]) * (1.0f/32768.0f);
peak = MAX(peak, v);
}
// 2) time delta
float dt = static_cast<float>(length) / sampleRate_;
// 3) update currentLevel_ with attack/decay
if (peak > currentLevel_) {
float riseFactor = 1.0f - fl::exp(-attackRate_ * dt);
currentLevel_ += (peak - currentLevel_) * riseFactor;
} else {
float decayFactor = fl::exp(-decayRate_ * dt);
currentLevel_ *= decayFactor;
}
// 4) output inertia: smooth smoothedOutput_ → currentLevel_
float outFactor = 1.0f - fl::exp(-outputRate_ * dt);
smoothedOutput_ += (currentLevel_ - smoothedOutput_) * outFactor;
return smoothedOutput_;
}
private:
float attackRate_; // = 1/τ₁
float decayRate_; // = 1/τ₂
float outputRate_; // = 1/τ₃
float sampleRate_;
float currentLevel_; // instantaneous peak with attack/decay
float smoothedOutput_; // returned value with inertia
};

View File

@@ -0,0 +1,253 @@
/// @file Audio.ino
/// @brief Audio visualization example with XY mapping
/// @example Audio.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
/*
This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled
Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable
all the UI elements you see below.
*/
#include <Arduino.h>
#include <FastLED.h>
#include "fl/audio.h"
#include "fl/downscale.h"
#include "fl/draw_visitor.h"
#include "fl/fft.h"
#include "fl/math.h"
#include "fl/math_macros.h"
#include "fl/raster.h"
#include "fl/time_alpha.h"
#include "fl/ui.h"
#include "fl/xypath.h"
#include "fl/unused.h"
#include "fx/time.h"
#include "fl/function.h"
// Sketch.
#include "fx_audio.h"
#include "fl/memfill.h"
using namespace fl;
#define HEIGHT 128
#define WIDTH 128
#define NUM_LEDS ((WIDTH) * (HEIGHT))
#define IS_SERPINTINE false
#define TIME_ANIMATION 1000 // ms
#define PIN_DATA 3
UITitle title("Simple control of an xy path");
UIDescription description("This is more of a test for new features.");
UICheckbox enableVolumeVis("Enable volume visualization", false);
UICheckbox enableRMS("Enable RMS visualization", false);
UICheckbox enableFFT("Enable FFT visualization", true);
UICheckbox freeze("Freeze frame", false);
UIButton advanceFrame("Advance frame");
UISlider decayTimeSeconds("Fade time Seconds", .1, 0, 4, .02);
UISlider attackTimeSeconds("Attack time Seconds", .1, 0, 4, .02);
UISlider outputTimeSec("outputTimeSec", .17, 0, 2, .01);
UIAudio audio("Audio");
UISlider fadeToBlack("Fade to black by", 5, 0, 20, 1);
// Group related UI elements using UIGroup template multi-argument constructor
UIGroup visualizationControls("Visualization", enableVolumeVis, enableRMS, enableFFT);
UIGroup audioProcessingControls("Audio Processing", decayTimeSeconds, attackTimeSeconds, outputTimeSec);
UIGroup generalControls("General Controls", freeze, advanceFrame, fadeToBlack);
MaxFadeTracker audioFadeTracker(attackTimeSeconds.value(),
decayTimeSeconds.value(), outputTimeSec.value(),
44100);
CRGB framebuffer[NUM_LEDS];
XYMap frameBufferXY(WIDTH, HEIGHT, IS_SERPINTINE);
CRGB leds[NUM_LEDS / 4]; // Downscaled buffer
XYMap ledsXY(WIDTH / 2, HEIGHT / 2,
IS_SERPINTINE); // Framebuffer is regular rectangle LED matrix.
FFTBins fftOut(WIDTH); // 2x width due to super sampling.
// CRGB framebuffer[NUM_LEDS];
// CRGB framebuffer[WIDTH_2X * HEIGHT_2X]; // 2x super sampling.
// XYMap frameBufferXY(WIDTH, HEIGHT, IS_SERPINTINE); // LED output, serpentine
// as is common for LED matrices. XYMap xyMap_2X(WIDTH_2X, HEIGHT_2X, false); //
// Framebuffer is regular rectangle LED matrix.
int x = 0;
int y = 0;
bool triggered = false;
SoundLevelMeter soundLevelMeter(.0, 0.0);
float rms(Slice<const int16_t> data) {
double sumSq = 0.0;
const int N = data.size();
for (int i = 0; i < N; ++i) {
int32_t x32 = int32_t(data[i]);
sumSq += x32 * x32;
}
float rms = sqrt(float(sumSq) / N);
return rms;
}
void setup() {
Serial.begin(115200);
// auto screenmap = frameBufferXY.toScreenMap();
// screenmap.setDiameter(.2);
// FastLED.addLeds<NEOPIXEL, 2>(framebuffer,
// NUM_LEDS).setScreenMap(screenmap);
auto screenmap = ledsXY.toScreenMap();
screenmap.setDiameter(.2);
decayTimeSeconds.onChanged([](float value) {
audioFadeTracker.setDecayTime(value);
FASTLED_WARN("Fade time seconds: " << value);
});
attackTimeSeconds.onChanged([](float value) {
audioFadeTracker.setAttackTime(value);
FASTLED_WARN("Attack time seconds: " << value);
});
outputTimeSec.onChanged([](float value) {
audioFadeTracker.setOutputTime(value);
FASTLED_WARN("Output time seconds: " << value);
});
FastLED.addLeds<NEOPIXEL, PIN_DATA>(leds, ledsXY.getTotal())
.setScreenMap(screenmap);
}
void shiftUp() {
// fade each led by 1%
if (fadeToBlack.as_int()) {
for (int i = 0; i < NUM_LEDS; ++i) {
auto &c = framebuffer[i];
c.fadeToBlackBy(fadeToBlack.as_int());
}
}
for (int y = HEIGHT - 1; y > 0; --y) {
CRGB* row1 = &framebuffer[frameBufferXY(0, y)];
CRGB* row2 = &framebuffer[frameBufferXY(0, y - 1)];
memcpy(row1, row2, WIDTH * sizeof(CRGB));
}
CRGB* row = &framebuffer[frameBufferXY(0, 0)];
fl::memfill(row, 0, sizeof(CRGB) * WIDTH);
}
bool doFrame() {
if (!freeze) {
return true;
}
if (advanceFrame.isPressed()) {
return true;
}
return false;
}
void loop() {
if (triggered) {
FASTLED_WARN("Triggered");
}
// x = pointX.as_int();
y = HEIGHT / 2;
bool do_frame = doFrame();
while (AudioSample sample = audio.next()) {
if (!do_frame) {
continue;
}
float fade = audioFadeTracker(sample.pcm().data(), sample.pcm().size());
shiftUp();
// FASTLED_WARN("Audio sample size: " << sample.pcm().size());
soundLevelMeter.processBlock(sample.pcm());
// FASTLED_WARN("")
auto dbfs = soundLevelMeter.getDBFS();
FASTLED_UNUSED(dbfs);
// FASTLED_WARN("getDBFS: " << dbfs);
int32_t max = 0;
for (size_t i = 0; i < sample.pcm().size(); ++i) {
int32_t x = ABS(sample.pcm()[i]);
if (x > max) {
max = x;
}
}
float anim =
fl::map_range<float, float>(max, 0.0f, 32768.0f, 0.0f, 1.0f);
anim = fl::clamp(anim, 0.0f, 1.0f);
x = fl::map_range<float, float>(anim, 0.0f, 1.0f, 0.0f, WIDTH - 1);
// FASTLED_WARN("x: " << x);
// fft.run(sample.pcm(), &fftOut);
sample.fft(&fftOut);
// FASTLED_ASSERT(fftOut.bins_raw.size() == WIDTH_2X,
// "FFT bins size mismatch");
if (enableFFT) {
auto max_x = fftOut.bins_raw.size() - 1;
FASTLED_UNUSED(max_x);
for (size_t i = 0; i < fftOut.bins_raw.size(); ++i) {
auto x = i;
auto v = fftOut.bins_db[i];
// Map audio intensity to a position in the heat palette (0-255)
v = fl::map_range<float, float>(v, 45, 70, 0, 1.f);
v = fl::clamp(v, 0.0f, 1.0f);
uint8_t heatIndex =
fl::map_range<float, uint8_t>(v, 0, 1, 0, 255);
// FASTLED_WARN(v);
// Use FastLED's built-in HeatColors palette
auto c = ColorFromPalette(HeatColors_p, heatIndex);
c.fadeToBlackBy(255 - heatIndex);
framebuffer[frameBufferXY(x, 0)] = c;
// FASTLED_WARN("y: " << i << " b: " << b);
}
}
if (enableVolumeVis) {
framebuffer[frameBufferXY(x, HEIGHT / 2)] = CRGB(0, 255, 0);
}
if (enableRMS) {
float rms = sample.rms();
FASTLED_WARN("RMS: " << rms);
rms = fl::map_range<float, float>(rms, 0.0f, 32768.0f, 0.0f, 1.0f);
rms = fl::clamp(rms, 0.0f, 1.0f) * WIDTH;
framebuffer[frameBufferXY(rms, HEIGHT * 3 / 4)] = CRGB(0, 0, 255);
}
if (true) {
uint16_t fade_width = fade * (WIDTH - 1);
uint16_t h = HEIGHT / 4;
// yellow
int index = frameBufferXY(fade_width, h);
auto c = CRGB(255, 255, 0);
framebuffer[index] = c;
}
}
// now downscale the framebuffer to the led matrix
downscale(framebuffer, frameBufferXY, leds, ledsXY);
FastLED.show();
}

View File

@@ -0,0 +1,99 @@
// I2S Audio Example for ESP32
// This example demonstrates using I2S audio input to control FastLED strips
// Based on audio levels from microphone or line input
//
// This example uses the extremely popular (as of 2025-September) INMP441 microphone.
// Notes:
// - Connect L/R to PWR so it's recognized as a right channel microphone.
#include <Arduino.h>
#include <FastLED.h>
#include "fl/sstream.h"
#include "fl/type_traits.h"
#include "fl/audio_input.h"
#include "platforms/esp/32/audio/sound_util.h"
using fl::i16;
// I2S Configuration
#define I2S_WS_PIN 7 // Word Select (LRCLK)
#define I2S_SD_PIN 8 // Serial Data (DIN)
#define I2S_CLK_PIN 4 // Serial Clock (BCLK)
#define I2S_CHANNEL fl::Right
fl::AudioConfig config = fl::AudioConfig::CreateInmp441(I2S_WS_PIN, I2S_SD_PIN, I2S_CLK_PIN, I2S_CHANNEL);
fl::shared_ptr<fl::IAudioInput> audioSource;
void setup() {
Serial.begin(115200);
Serial.println("I2S Audio FastLED Example");
Serial.println("Waiting 5000ms for audio device to stdout initialization...");
delay(5000);
// Initialize I2S Audio
fl::string errorMsg;
audioSource = fl::IAudioInput::create(config, &errorMsg);
if (!audioSource) {
Serial.print("Failed to create audio source: ");
Serial.println(errorMsg.c_str());
return;
}
// Start audio capture
Serial.println("Starting audio capture...");
audioSource->start();
// Check for start errors
fl::string startErrorMsg;
if (audioSource->error(&startErrorMsg)) {
Serial.print("Audio start error: ");
Serial.println(startErrorMsg.c_str());
return;
}
Serial.println("Audio capture started!");
}
void loop() {
EVERY_N_MILLIS(1000) { Serial.println("loop active."); }
// Check if audio source is valid
if (!audioSource) {
Serial.println("Audio source is null!");
delay(1000);
return;
}
// Check for audio errors
fl::string errorMsg;
if (audioSource->error(&errorMsg)) {
Serial.print("Audio error: ");
Serial.println(errorMsg.c_str());
delay(100);
return;
}
// Read audio data
fl::AudioSample sample = audioSource->read();
if (sample.isValid()) {
EVERY_N_MILLIS(100) {
const auto& audioBuffer = sample.pcm();
const i16* max_sample = fl::max_element(audioBuffer.begin(), audioBuffer.end());
const i16* min_sample = fl::min_element(audioBuffer.begin(), audioBuffer.end());
fl::sstream ss;
ss << "\nRead " << audioBuffer.size() << " samples, timestamp: " << sample.timestamp() << "ms\n";
ss << "Max sample: " << *max_sample << "\n";
ss << "Min sample: " << *min_sample << "\n";
ss << "RMS: " << sample.rms() << "\n";
ss << "ZCF: " << sample.zcf() << "\n";
FL_WARN(ss.str());
}
}
}

View File

@@ -0,0 +1,11 @@
// I2S Audio Example (for ESP32 as of 2025-September)
// This example demonstrates using I2S audio input to control FastLED strips
// Based on audio levels from microphone or line input
#include "fl/audio_input.h"
#if FASTLED_HAS_AUDIO_INPUT
#include "./AudioInput.h"
#else
#include "platforms/sketch_fake.hpp"
#endif // FASTLED_HAS_AUDIO_INPUT

View File

@@ -0,0 +1,83 @@
/// @file Blink.ino
/// @brief Blink the first LED of an LED strip
/// @example Blink.ino
#include <Arduino.h>
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS 1
// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 3
#define CLOCK_PIN 13
// Define the array of leds
CRGB leds[NUM_LEDS];
void setup() {
//Serial.begin(9600);
//Serial.println("BLINK setup starting");
// Uncomment/edit one of the following lines for your leds arrangement.
// ## Clockless types ##
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // GRB ordering is assumed
//Serial.println("BLINK setup complete");
// FastLED.addLeds<SM16824E, DATA_PIN, RGB>(leds, NUM_LEDS); // RGB ordering (uses SM16824EController)
// FastLED.addLeds<SM16703, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1829, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1812, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1809, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1804, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1803, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1903B, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1904, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS2903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2852, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<GS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SK6812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<APA106, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<PL9823, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2813, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<APA104, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2811_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GE8822, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GW6205, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GW6205_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<LPD1886, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<LPD1886_8BIT, DATA_PIN, RGB>(leds, NUM_LEDS);
// ## Clocked (SPI) types ##
// FastLED.addLeds<LPD6803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<LPD8806, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2801, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SM16716, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<P9813, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<DOTSTAR, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<SK9822, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
}
void loop() {
//Serial.println("BLINK");
// Turn the LED on, then pause
leds[0] = CRGB::Red;
FastLED.show();
delay(500);
// Now turn the LED off, then pause
leds[0] = CRGB::Black;
FastLED.show();
delay(500);
}

View File

@@ -0,0 +1,96 @@
/// @file BlinkParallel.ino
/// @brief Shows parallel usage of WS2812 strips. Blinks once for red, twice for green, thrice for blue.
/// @example BlinkParallel.ino
#include "FastLED.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
// How many leds in your strip?
#define NUM_LEDS 256
#else
#define NUM_LEDS 16
#endif
// Demo of driving multiple WS2812 strips on different pins
// Define the array of leds
CRGB leds[NUM_LEDS]; // Yes, they all share a buffer.
void setup() {
Serial.begin(115200);
//FastLED.addLeds<WS2812, 5>(leds, NUM_LEDS); // GRB ordering is assumed
FastLED.addLeds<WS2812, 1>(leds, NUM_LEDS); // GRB ordering is assumed
FastLED.addLeds<WS2812, 2>(leds, NUM_LEDS); // GRB ordering is assumed
FastLED.addLeds<WS2812, 3>(leds, NUM_LEDS); // GRB ordering is assumed
FastLED.addLeds<WS2812, 4>(leds, NUM_LEDS); // GRB ordering is assumed
FL_WARN("Initialized 4 LED strips with " << NUM_LEDS << " LEDs each");
FL_WARN("Setup complete - starting blink animation");
delay(1000);
}
void fill(CRGB color) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = color;
}
}
void blink(CRGB color, int times) {
for (int i = 0; i < times; i++) {
fill(color);
FastLED.show();
delay(500);
fill(CRGB::Black);
FastLED.show();
delay(500);
}
}
void loop() {
static int loopCount = 0;
EVERY_N_MILLISECONDS(1000) { // Every 1 second (faster for QEMU testing)
loopCount++;
FL_WARN("Starting loop iteration " << loopCount);
// Add completion marker after a few loops for QEMU testing
if (loopCount >= 2) { // Complete after 2 iterations instead of 3
FL_WARN("FL_WARN test finished - completed " << loopCount << " iterations");
}
}
// Turn the LED on, then pause
blink(CRGB(8,0,0), 1); // blink once for red
blink(CRGB(0,8,0), 2); // blink twice for green
blink(CRGB(0,0,8), 3); // blink thrice for blue
delay(50);
// now benchmark
uint32_t start = millis();
fill(CRGB(8,8,8));
FastLED.show();
uint32_t diff = millis() - start;
Serial.print("Time to fill and show for non blocking (ms): ");
Serial.println(diff);
EVERY_N_MILLISECONDS(500) { // Every 0.5 seconds (faster for QEMU testing)
FL_WARN("FastLED.show() timing: " << diff << "ms");
}
delay(50);
start = millis();
fill(CRGB(8,8,8));
FastLED.show();
FastLED.show();
diff = millis() - start;
Serial.print("Time to fill and show for 2nd blocking (ms): ");
Serial.println(diff);
}

View File

@@ -0,0 +1,32 @@
// UIDescription: This example shows how to blur a strip of LEDs. It uses the blur1d function to blur the strip and fadeToBlackBy to dim the strip. A bright pixel moves along the strip.
// Author: Zach Vorhies
#include <FastLED.h>
#define NUM_LEDS 64
#define DATA_PIN 3 // Change this to match your LED strip's data pin
#define BRIGHTNESS 255
CRGB leds[NUM_LEDS];
uint8_t pos = 0;
bool toggle = false;
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
}
void loop() {
// Add a bright pixel that moves
leds[pos] = CHSV(pos * 2, 255, 255);
// Blur the entire strip
blur1d(leds, NUM_LEDS, 172);
fadeToBlackBy(leds, NUM_LEDS, 16);
FastLED.show();
// Move the position of the dot
if (toggle) {
pos = (pos + 1) % NUM_LEDS;
}
toggle = !toggle;
delay(20);
}

View File

@@ -0,0 +1,65 @@
/// @file Blur2d.ino
/// @brief Demonstrates 2D blur effects on LED matrix
/// @example Blur2d.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
// UIDescription: This example shows how to blur a strip of LEDs in 2d.
#include <Arduino.h>
#include <FastLED.h>
#include "fl/ui.h"
#include "fl/xymap.h"
using namespace fl;
#if SKETCH_HAS_LOTS_OF_MEMORY
#define WIDTH 22
#define HEIGHT 22
#else
#define WIDTH 12
#define HEIGHT 12
#endif
#define NUM_LEDS (WIDTH * HEIGHT)
#define BLUR_AMOUNT 172
#define DATA_PIN 2 // Change this to match your LED strip's data pin
#define BRIGHTNESS 255
#define SERPENTINE true
CRGB leds[NUM_LEDS];
uint8_t pos = 0;
bool toggle = false;
XYMap xymap(WIDTH, HEIGHT, SERPENTINE);
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS)
.setScreenMap(xymap); // Necessary when using the FastLED web compiler to display properly on a web page.
FastLED.setBrightness(BRIGHTNESS);
Serial.println("setup");
}
void loop() {
static int x = random(WIDTH);
static int y = random(HEIGHT);
static CRGB c = CRGB(0, 0, 0);
blur2d(leds, WIDTH, HEIGHT, BLUR_AMOUNT, xymap);
EVERY_N_MILLISECONDS(1000) {
x = random(WIDTH);
y = random(HEIGHT);
uint8_t r = random(255);
uint8_t g = random(255);
uint8_t b = random(255);
c = CRGB(r, g, b);
}
leds[xymap(x, y)] = c;
FastLED.show();
delay(20);
}

View File

@@ -0,0 +1,607 @@
/// @file Chromancer.ino
/// @brief Hexagonal LED display visualization
/// @example Chromancer.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
/*
Original Source: https://github.com/ZackFreedman/Chromance
GaryWoo's Video: https://www.youtube.com/watch?v=-nSCtxa2Kp0
GaryWoo's LedMap: https://gist.github.com/Garywoo/b6cd1ea90cb5e17cc60b01ae68a2b770
GaryWoo's presets: https://gist.github.com/Garywoo/82fa67c6e1f9529dc16a01dd97d05d58
Chromance wall hexagon source (emotion controlled w/ EmotiBit)
Partially cribbed from the DotStar example
I smooshed in the ESP32 BasicOTA sketch, too
(C) Voidstar Lab 2021
*/
#include "fl/sketch_macros.h"
#include "fl/warn.h"
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
// Other platforms have weird issues. Will revisit this later.
#include <Arduino.h>
void setup() {
// Use Serial.println instead of FL_WARN to prevent optimization away
Serial.begin(115200);
Serial.println("Chromancer.ino: setup() - Platform has insufficient memory for full demo");
}
void loop() {
// Use Serial.println instead of FL_WARN to prevent optimization away
Serial.println("Chromancer.ino: loop() - Platform has insufficient memory for full demo");
delay(1000); // Prevent rapid printing
}
#else
#include <FastLED.h>
#include "fl/screenmap.h"
#include "fl/math_macros.h"
#include "fl/json.h"
#include "fl/ui.h"
#include "fl/map.h"
#include "fl/str.h"
#include "./screenmap.json.h"
#include "./mapping.h"
#include "./ripple.h"
#include "./detail.h"
using namespace fl;
enum {
BlackStrip = 0,
GreenStrip = 1,
RedStrip = 2,
BlueStrip = 3,
};
// Strips are different lengths because I am a dumb
constexpr int lengths[] = {
154, // Black strip
168, // Green strip
84, // Red strip
154 // Blue strip
};
// non emscripten uses separate arrays for each strip. Eventually emscripten
// should support this as well but right now we don't
CRGB leds0[lengths[BlackStrip]] = {};
CRGB leds1[lengths[GreenStrip]] = {};
CRGB leds2[lengths[RedStrip]] = {}; // Red
CRGB leds3[lengths[BlueStrip]] = {};
CRGB *leds[] = {leds0, leds1, leds2, leds3};
byte ledColors[40][14][3]; // LED buffer - each ripple writes to this, then we
// write this to the strips
//float decay = 0.97; // Multiply all LED's by this amount each tick to create
// fancy fading tails
UISlider sliderDecay("decay", .97f, .8, 1.0, .01);
// These ripples are endlessly reused so we don't need to do any memory
// management
#define numberOfRipples 30
Ripple ripples[numberOfRipples] = {
Ripple(0), Ripple(1), Ripple(2), Ripple(3), Ripple(4), Ripple(5),
Ripple(6), Ripple(7), Ripple(8), Ripple(9), Ripple(10), Ripple(11),
Ripple(12), Ripple(13), Ripple(14), Ripple(15), Ripple(16), Ripple(17),
Ripple(18), Ripple(19), Ripple(20), Ripple(21), Ripple(22), Ripple(23),
Ripple(24), Ripple(25), Ripple(26), Ripple(27), Ripple(28), Ripple(29),
};
// Biometric detection and interpretation
// IR (heartbeat) is used to fire outward ripples
float lastIrReading; // When our heart pumps, reflected IR drops sharply
float highestIrReading; // These vars let us detect this drop
unsigned long
lastHeartbeat; // Track last heartbeat so we can detect noise/disconnections
#define heartbeatLockout \
500 // Heartbeats that happen within this many milliseconds are ignored
#define heartbeatDelta 300 // Drop in reflected IR that constitutes a heartbeat
// Heartbeat color ripples are proportional to skin temperature
#define lowTemperature 33.0 // Resting temperature
#define highTemperature 37.0 // Really fired up
float lastKnownTemperature =
(lowTemperature + highTemperature) /
2.0; // Carries skin temperature from temperature callback to IR callback
// EDA code was too unreliable and was cut.
// TODO: Rebuild EDA code
// Gyroscope is used to reject data if you're moving too much
#define gyroAlpha 0.9 // Exponential smoothing constant
#define gyroThreshold \
300 // Minimum angular velocity total (X+Y+Z) that disqualifies readings
float gyroX, gyroY, gyroZ;
// If you don't have an EmotiBit or don't feel like wearing it, that's OK
// We'll fire automatic pulses
#define randomPulsesEnabled true // Fire random rainbow pulses from random nodes
#define cubePulsesEnabled true // Draw cubes at random nodes
UICheckbox starburstPulsesEnabled("Starburst Pulses", true);
UICheckbox simulatedBiometricsEnabled("Simulated Biometrics", true);
#define autoPulseTimeout \
5000 // If no heartbeat is received in this many ms, begin firing
// random/simulated pulses
#define randomPulseTime 2000 // Fire a random pulse every (this many) ms
unsigned long lastRandomPulse;
byte lastAutoPulseNode = 255;
byte numberOfAutoPulseTypes =
randomPulsesEnabled + cubePulsesEnabled + int(starburstPulsesEnabled);
byte currentAutoPulseType = 255;
#define autoPulseChangeTime 30000
unsigned long lastAutoPulseChange;
#define simulatedHeartbeatBaseTime \
600 // Fire a simulated heartbeat pulse after at least this many ms
#define simulatedHeartbeatVariance \
200 // Add random jitter to simulated heartbeat
#define simulatedEdaBaseTime 1000 // Same, but for inward EDA pulses
#define simulatedEdaVariance 10000
unsigned long nextSimulatedHeartbeat;
unsigned long nextSimulatedEda;
// Helper function to check if a node is on the border
bool isNodeOnBorder(byte node) {
for (int i = 0; i < numberOfBorderNodes; i++) {
if (node == borderNodes[i]) {
return true;
}
}
return false;
}
UITitle title("Chromancer");
UIDescription description("Take 6 seconds to boot up. Chromancer is a wall-mounted hexagonal LED display that originally reacted to biometric data from an EmotiBit sensor. It visualizes your heartbeat, skin temperature, and movement in real-time. Chromancer also has a few built-in effects that can be triggered with the push of a button. Enjoy!");
UICheckbox allWhite("All White", false);
UIButton simulatedHeartbeat("Simulated Heartbeat");
UIButton triggerStarburst("Trigger Starburst");
UIButton triggerRainbowCube("Rainbow Cube");
UIButton triggerBorderWave("Border Wave");
UIButton triggerSpiral("Spiral Wave");
bool wasHeartbeatClicked = false;
bool wasStarburstClicked = false;
bool wasRainbowCubeClicked = false;
bool wasBorderWaveClicked = false;
bool wasSpiralClicked = false;
// Group related UI elements using UIGroup template multi-argument constructor
UIGroup effectTriggers("Effect Triggers", simulatedHeartbeat, triggerStarburst, triggerRainbowCube, triggerBorderWave, triggerSpiral);
UIGroup automationControls("Automation", starburstPulsesEnabled, simulatedBiometricsEnabled);
UIGroup displayControls("Display", sliderDecay, allWhite);
void setup() {
Serial.begin(115200);
Serial.println("*** LET'S GOOOOO ***");
Serial.println("JSON SCREENMAP");
Serial.println(JSON_SCREEN_MAP);
fl::fl_map<fl::string, ScreenMap> segmentMaps;
ScreenMap::ParseJson(JSON_SCREEN_MAP, &segmentMaps);
printf("Parsed %d segment maps\n", int(segmentMaps.size()));
for (auto kv : segmentMaps) {
Serial.print(kv.first.c_str());
Serial.print(" ");
Serial.println(kv.second.getLength());
}
// ScreenMap screenmaps[4];
ScreenMap red, black, green, blue;
bool ok = true;
auto red_it = segmentMaps.find("red_segment");
ok = (red_it != segmentMaps.end()) && ok;
if (red_it != segmentMaps.end()) red = red_it->second;
auto black_it = segmentMaps.find("back_segment");
ok = (black_it != segmentMaps.end()) && ok;
if (black_it != segmentMaps.end()) black = black_it->second;
auto green_it = segmentMaps.find("green_segment");
ok = (green_it != segmentMaps.end()) && ok;
if (green_it != segmentMaps.end()) green = green_it->second;
auto blue_it = segmentMaps.find("blue_segment");
ok = (blue_it != segmentMaps.end()) && ok;
if (blue_it != segmentMaps.end()) blue = blue_it->second;
if (!ok) {
Serial.println("Failed to get all segment maps");
return;
}
CRGB* red_leds = leds[RedStrip];
CRGB* black_leds = leds[BlackStrip];
CRGB* green_leds = leds[GreenStrip];
CRGB* blue_leds = leds[BlueStrip];
FastLED.addLeds<WS2812, 2>(black_leds, lengths[BlackStrip]).setScreenMap(black);
FastLED.addLeds<WS2812, 3>(green_leds, lengths[GreenStrip]).setScreenMap(green);
FastLED.addLeds<WS2812, 1>(red_leds, lengths[RedStrip]).setScreenMap(red);
FastLED.addLeds<WS2812, 4>(blue_leds, lengths[BlueStrip]).setScreenMap(blue);
FastLED.show();
}
void loop() {
unsigned long benchmark = millis();
FL_UNUSED(benchmark);
// Fade all dots to create trails
for (int strip = 0; strip < 40; strip++) {
for (int led = 0; led < 14; led++) {
for (int i = 0; i < 3; i++) {
ledColors[strip][led][i] *= sliderDecay.value();
}
}
}
for (int i = 0; i < numberOfRipples; i++) {
ripples[i].advance(ledColors);
}
for (int segment = 0; segment < 40; segment++) {
for (int fromBottom = 0; fromBottom < 14; fromBottom++) {
int strip = ledAssignments[segment][0];
int led = round(fmap(fromBottom, 0, 13, ledAssignments[segment][2],
ledAssignments[segment][1]));
leds[strip][led] = CRGB(ledColors[segment][fromBottom][0],
ledColors[segment][fromBottom][1],
ledColors[segment][fromBottom][2]);
}
}
if (allWhite) {
// for all strips
for (int i = 0; i < 4; i++) {
for (int j = 0; j < lengths[i]; j++) {
leds[i][j] = CRGB::White;
}
}
}
FastLED.show();
// Check if buttons were clicked
wasHeartbeatClicked = bool(simulatedHeartbeat);
wasStarburstClicked = bool(triggerStarburst);
wasRainbowCubeClicked = bool(triggerRainbowCube);
wasBorderWaveClicked = bool(triggerBorderWave);
wasSpiralClicked = bool(triggerSpiral);
if (wasSpiralClicked) {
// Trigger spiral wave effect from center
unsigned int baseColor = random(0xFFFF);
byte centerNode = 15; // Center node
// Create 6 ripples in a spiral pattern
for (int i = 0; i < 6; i++) {
if (nodeConnections[centerNode][i] >= 0) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
centerNode, i,
Adafruit_DotStar_ColorHSV(
baseColor + (0xFFFF / 6) * i, 255, 255),
0.3 + (i * 0.1), // Varying speeds creates spiral effect
2000,
i % 2 ? alwaysTurnsLeft : alwaysTurnsRight); // Alternating turn directions
break;
}
}
}
}
lastHeartbeat = millis();
}
if (wasBorderWaveClicked) {
// Trigger immediate border wave effect
unsigned int baseColor = random(0xFFFF);
// Start ripples from each border node in sequence
for (int i = 0; i < numberOfBorderNodes; i++) {
byte node = borderNodes[i];
// Find an inward direction
for (int dir = 0; dir < 6; dir++) {
if (nodeConnections[node][dir] >= 0 &&
!isNodeOnBorder(nodeConnections[node][dir])) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
node, dir,
Adafruit_DotStar_ColorHSV(
baseColor + (0xFFFF / numberOfBorderNodes) * i,
255, 255),
.4, 2000, 0);
break;
}
}
break;
}
}
}
lastHeartbeat = millis();
}
if (wasRainbowCubeClicked) {
// Trigger immediate rainbow cube effect
int node = cubeNodes[random(numberOfCubeNodes)];
unsigned int baseColor = random(0xFFFF);
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
for (int i = 0; i < 6; i++) {
if (nodeConnections[node][i] >= 0) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
node, i,
Adafruit_DotStar_ColorHSV(
baseColor + (0xFFFF / 6) * i, 255, 255),
.5, 2000, behavior);
break;
}
}
}
}
lastHeartbeat = millis();
}
if (wasStarburstClicked) {
// Trigger immediate starburst effect
unsigned int baseColor = random(0xFFFF);
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
starburstNode, i,
Adafruit_DotStar_ColorHSV(
baseColor + (0xFFFF / 6) * i, 255, 255),
.65, 1500, behavior);
break;
}
}
}
lastHeartbeat = millis();
}
if (wasHeartbeatClicked) {
// Trigger immediate heartbeat effect
for (int i = 0; i < 6; i++) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(15, i, 0xEE1111,
float(random(100)) / 100.0 * .1 + .4, 1000, 0);
break;
}
}
}
lastHeartbeat = millis();
}
if (millis() - lastHeartbeat >= autoPulseTimeout) {
// When biometric data is unavailable, visualize at random
if (numberOfAutoPulseTypes &&
millis() - lastRandomPulse >= randomPulseTime) {
unsigned int baseColor = random(0xFFFF);
if (currentAutoPulseType == 255 ||
(numberOfAutoPulseTypes > 1 &&
millis() - lastAutoPulseChange >= autoPulseChangeTime)) {
byte possiblePulse = 255;
while (true) {
possiblePulse = random(3);
if (possiblePulse == currentAutoPulseType)
continue;
switch (possiblePulse) {
case 0:
if (!randomPulsesEnabled)
continue;
break;
case 1:
if (!cubePulsesEnabled)
continue;
break;
case 2:
if (!starburstPulsesEnabled)
continue;
break;
default:
continue;
}
currentAutoPulseType = possiblePulse;
lastAutoPulseChange = millis();
break;
}
}
switch (currentAutoPulseType) {
case 0: {
int node = 0;
bool foundStartingNode = false;
while (!foundStartingNode) {
node = random(25);
foundStartingNode = true;
for (int i = 0; i < numberOfBorderNodes; i++) {
// Don't fire a pulse on one of the outer nodes - it
// looks boring
if (node == borderNodes[i])
foundStartingNode = false;
}
if (node == lastAutoPulseNode)
foundStartingNode = false;
}
lastAutoPulseNode = node;
for (int i = 0; i < 6; i++) {
if (nodeConnections[node][i] >= 0) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
node, i,
// strip0.ColorHSV(baseColor
// + (0xFFFF / 6) * i,
// 255, 255),
Adafruit_DotStar_ColorHSV(baseColor, 255,
255),
float(random(100)) / 100.0 * .2 + .5, 3000,
1);
break;
}
}
}
}
break;
}
case 1: {
int node = cubeNodes[random(numberOfCubeNodes)];
while (node == lastAutoPulseNode)
node = cubeNodes[random(numberOfCubeNodes)];
lastAutoPulseNode = node;
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
for (int i = 0; i < 6; i++) {
if (nodeConnections[node][i] >= 0) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
node, i,
// strip0.ColorHSV(baseColor
// + (0xFFFF / 6) * i,
// 255, 255),
Adafruit_DotStar_ColorHSV(baseColor, 255,
255),
.5, 2000, behavior);
break;
}
}
}
}
break;
}
case 2: {
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
lastAutoPulseNode = starburstNode;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
starburstNode, i,
Adafruit_DotStar_ColorHSV(
baseColor + (0xFFFF / 6) * i, 255, 255),
.65, 1500, behavior);
break;
}
}
}
break;
}
default:
break;
}
lastRandomPulse = millis();
}
if (simulatedBiometricsEnabled) {
// Simulated heartbeat
if (millis() >= nextSimulatedHeartbeat) {
for (int i = 0; i < 6; i++) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
ripples[j].start(
15, i, 0xEE1111,
float(random(100)) / 100.0 * .1 + .4, 1000, 0);
break;
}
}
}
nextSimulatedHeartbeat = millis() + simulatedHeartbeatBaseTime +
random(simulatedHeartbeatVariance);
}
// Simulated EDA ripples
if (millis() >= nextSimulatedEda) {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < numberOfRipples; j++) {
if (ripples[j].state == dead) {
byte targetNode =
borderNodes[random(numberOfBorderNodes)];
byte direction = 255;
while (direction == 255) {
direction = random(6);
if (nodeConnections[targetNode][direction] < 0)
direction = 255;
}
ripples[j].start(
targetNode, direction, 0x1111EE,
float(random(100)) / 100.0 * .5 + 2, 300, 2);
break;
}
}
}
nextSimulatedEda = millis() + simulatedEdaBaseTime +
random(simulatedEdaVariance);
}
}
}
// Serial.print("Benchmark: ");
// Serial.println(millis() - benchmark);
}
#endif // __AVR__

View File

@@ -0,0 +1,80 @@
#pragma once
#include "fl/stdint.h"
inline uint32_t Adafruit_DotStar_ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
uint8_t r, g, b;
// Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
// 0 is not the start of pure red, but the midpoint...a few values above
// zero and a few below 65536 all yield pure red (similarly, 32768 is the
// midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
// each for red, green, blue) really only allows for 1530 distinct hues
// (not 1536, more on that below), but the full unsigned 16-bit type was
// chosen for hue so that one's code can easily handle a contiguous color
// wheel by allowing hue to roll over in either direction.
hue = (hue * 1530L + 32768) / 65536;
// Because red is centered on the rollover point (the +32768 above,
// essentially a fixed-point +0.5), the above actually yields 0 to 1530,
// where 0 and 1530 would yield the same thing. Rather than apply a
// costly modulo operator, 1530 is handled as a special case below.
// So you'd think that the color "hexcone" (the thing that ramps from
// pure red, to pure yellow, to pure green and so forth back to red,
// yielding six slices), and with each color component having 256
// possible values (0-255), might have 1536 possible items (6*256),
// but in reality there's 1530. This is because the last element in
// each 256-element slice is equal to the first element of the next
// slice, and keeping those in there this would create small
// discontinuities in the color wheel. So the last element of each
// slice is dropped...we regard only elements 0-254, with item 255
// being picked up as element 0 of the next slice. Like this:
// Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
// Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
// Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
// and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
// the constants below are not the multiples of 256 you might expect.
// Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
if (hue < 510) { // Red to Green-1
b = 0;
if (hue < 255) { // Red to Yellow-1
r = 255;
g = hue; // g = 0 to 254
} else { // Yellow to Green-1
r = 510 - hue; // r = 255 to 1
g = 255;
}
} else if (hue < 1020) { // Green to Blue-1
r = 0;
if (hue < 765) { // Green to Cyan-1
g = 255;
b = hue - 510; // b = 0 to 254
} else { // Cyan to Blue-1
g = 1020 - hue; // g = 255 to 1
b = 255;
}
} else if (hue < 1530) { // Blue to Red-1
g = 0;
if (hue < 1275) { // Blue to Magenta-1
r = hue - 1020; // r = 0 to 254
b = 255;
} else { // Magenta to Red-1
r = 255;
b = 1530 - hue; // b = 255 to 1
}
} else { // Last 0.5 Red (quicker than % operator)
r = 255;
g = b = 0;
}
// Apply saturation and value to R,G,B, pack into 32-bit result:
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
uint16_t s1 = 1 + sat; // 1 to 256; same reason
uint8_t s2 = 255 - sat; // 255 to 0
return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
(((((g * s1) >> 8) + s2) * v1) & 0xff00) |
(((((b * s1) >> 8) + s2) * v1) >> 8);
}

View File

@@ -0,0 +1,47 @@
#pragma once
const char JSON_MAP[] = R"({"map": [
406,407,408,409,410,411,412,413,414,415,416,417,418,419,
420,421,422,423,424,425,426,427,428,429,430,431,432,433,
434,435,436,437,438,439,440,441,442,443,444,445,446,447,
532,533,534,535,536,537,538,539,540,541,542,543,544,545,
546,547,548,549,550,551,552,553,554,555,556,557,558,559,
377,376,375,374,373,372,371,370,369,368,367,366,365,364,
363,362,361,360,359,358,357,356,355,354,353,352,351,350,
392,393,394,395,396,397,398,399,400,401,402,403,404,405,
223,222,221,220,219,218,217,216,215,214,213,212,211,210,
125,124,123,122,121,120,119,118,117,116,115,114,113,112,
111,110,109,108,107,106,105,104,103,102,101,100,99,98,
97,96,95,94,93,92,91,90,89,88,87,86,85,84,
168,169,170,171,172,173,174,175,176,177,178,179,180,181,
182,183,184,185,186,187,188,189,190,191,192,193,194,195,
196,197,198,199,200,201,202,203,204,205,206,207,208,209,
126,127,128,129,130,131,132,133,134,135,136,137,138,139,
307,306,305,304,303,302,301,300,299,298,297,296,295,294,
349,348,347,346,345,344,343,342,341,340,339,338,337,336,
391,390,389,388,387,386,385,384,383,382,381,380,379,378,
13,12,11,10,9,8,7,6,5,4,3,2,1,0,
461,460,459,458,457,456,455,454,453,452,451,450,449,448,
531,530,529,528,527,526,525,524,523,522,521,520,519,518,
517,516,515,514,513,512,511,510,509,508,507,506,505,504,
503,502,501,500,499,498,497,496,495,494,493,492,491,490,
476,477,478,479,480,481,482,483,484,485,486,487,488,489,
321,320,319,318,317,316,315,314,313,312,311,310,309,308,
153,152,151,150,149,148,147,146,145,144,143,142,141,140,
322,323,324,325,326,327,328,329,330,331,332,333,334,335,
475,474,473,472,471,470,469,468,467,466,465,464,463,462,
154,155,156,157,158,159,160,161,162,163,164,165,166,167,
14,15,16,17,18,19,20,21,22,23,24,25,26,27,
28,29,30,31,32,33,34,35,36,37,38,39,40,41,
42,43,44,45,46,47,48,49,50,51,52,53,54,55,
56,57,58,59,60,61,62,63,64,65,66,67,68,69,
70,71,72,73,74,75,76,77,78,79,80,81,82,83,
237,236,235,234,233,232,231,230,229,228,227,226,225,224,
238,239,240,241,242,243,244,245,246,247,248,249,250,251,
252,253,254,255,256,257,258,259,260,261,262,263,264,265,
266,267,268,269,270,271,272,273,274,275,276,277,278,279,
280,281,282,283,284,285,286,287,288,289,290,291,292,293
]}
)";

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,270 @@
"""
Generates the hexegon using math.
"""
from dataclasses import dataclass
from enum import Enum
import json
from math import pi, cos, sin
LED_PER_STRIP = 14
SPACE_PER_LED = 30.0 # Increased for better visibility
LED_DIAMETER = SPACE_PER_LED / 4
MIRROR_X = True # Diagramed from the reverse side. Reverse the x-axis
SMALLEST_ANGLE = 360 / 6
class HexagonAngle(Enum):
UP = 90
DOWN = 270
RIGHT_UP = 30
RIGHT_DOWN = 360 - 30
LEFT_UP = 150 # (RIGHT_DOWN + 180) % 360
LEFT_DOWN = 210 # (RIGHT_UP + 180) % 360
def toRads(angle: float) -> float:
return angle * (pi / 180)
@dataclass
class Point:
x: float
y: float
@staticmethod
def toJson(points: list["Point"]) -> list[dict]:
x_values = [p.x for p in points]
y_values = [p.y for p in points]
# round
x_values = [round(x, 4) for x in x_values]
y_values = [round(y, 4) for y in y_values]
if MIRROR_X:
x_values = [-x for x in x_values]
return {"x": x_values, "y": y_values, "diameter": LED_DIAMETER}
def copy(self) -> "Point":
return Point(self.x, self.y)
def __repr__(self) -> str:
x_rounded = round(self.x, 2)
y_rounded = round(self.y, 2)
return f"({x_rounded}, {y_rounded})"
def next_point(pos: Point, angle: HexagonAngle, space: float) -> Point:
degrees = angle.value
angle_rad = toRads(degrees)
x = pos.x + space * cos(angle_rad)
y = pos.y + space * sin(angle_rad)
return Point(x, y)
def gen_points(
input: list[HexagonAngle], leds_per_strip: int, startPos: Point,
exclude: list[int] | None = None,
add_last: bool = False
) -> list[Point]:
points: list[Point] = []
if (not input) or (not leds_per_strip):
return points
exclude = exclude or []
# Start FSM. Start pointer get's put into the accumulator.
curr_point: Point = Point(startPos.x, startPos.y)
# points.append(curr_point)
last_angle = input[0]
for i,angle in enumerate(input):
excluded = i in exclude
values = list(range(leds_per_strip))
last_angle = angle
for v in values:
last_angle = angle
curr_point = next_point(curr_point, angle, SPACE_PER_LED)
if not excluded:
points.append(curr_point)
#if i == len(input) - 1:
# break
# Next starting point
curr_point = next_point(curr_point, last_angle, SPACE_PER_LED)
#if not excluded:
# points.append(curr_point)
if add_last:
points.append(curr_point)
return points
def main() -> None:
startPos = Point(0, 0)
hexagon_angles = [
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.RIGHT_DOWN,
HexagonAngle.DOWN,
HexagonAngle.LEFT_DOWN,
HexagonAngle.LEFT_UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
print(points)
def simple_test() -> None:
startPos = Point(0, 0)
hexagon_angles = [
HexagonAngle.UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
print(points)
# assert len(points) == LED_PER_STRIP + 1
def two_angle_test() -> None:
startPos = Point(0, 0)
hexagon_angles = [
HexagonAngle.UP,
HexagonAngle.UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
print(points)
# assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points"
def two_angle_test2() -> None:
print("two_angle_test2")
startPos = Point(0, 0)
hexagon_angles = [
HexagonAngle.UP,
HexagonAngle.DOWN,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
print(points)
# assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points"
# Red is defined by this instruction tutorial: https://voidstar.dozuki.com/Guide/Chromance+Assembly+Instructions/6
def find_red_anchor_point() -> list[Point]:
hexagon_angles = [
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_UP,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True)
return points
def find_green_anchore_point() -> list[Point]:
hexagon_angles = [
HexagonAngle.RIGHT_UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True)
return points
RED_ANCHOR_POINT = find_red_anchor_point()[-1]
BLACK_ANCHOR_POINT = Point(0,0) # Black
GREEN_ANCHOR_POINT = find_green_anchore_point()[-1]
BLUE_ANCHOR_POINT = Point(0, 0)
def generate_red_points() -> list[Point]:
starting_point = RED_ANCHOR_POINT.copy()
hexagon_angles = [
HexagonAngle.UP,
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_DOWN,
HexagonAngle.DOWN,
HexagonAngle.RIGHT_DOWN,
HexagonAngle.UP,
HexagonAngle.LEFT_UP
]
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5])
return points
def generate_black_points() -> list[Point]:
starting_point = BLACK_ANCHOR_POINT.copy()
hexagon_angles = [
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_UP,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.RIGHT_DOWN,
HexagonAngle.DOWN,
HexagonAngle.LEFT_DOWN,
HexagonAngle.UP,
HexagonAngle.LEFT_UP,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point)
return points
def generate_green_points() -> list[Point]:
starting_point = GREEN_ANCHOR_POINT.copy()
hexagon_angles = [
HexagonAngle.RIGHT_UP,
HexagonAngle.UP,
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_DOWN,
HexagonAngle.DOWN,
HexagonAngle.RIGHT_DOWN, # skip
HexagonAngle.LEFT_DOWN, # skip
HexagonAngle.LEFT_UP,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_DOWN,
HexagonAngle.RIGHT_DOWN,
HexagonAngle.RIGHT_UP, # skip
HexagonAngle.RIGHT_DOWN,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5,6,13])
return points
def generate_blue_points() -> list[Point]:
starting_point = BLUE_ANCHOR_POINT.copy()
hexagon_angles = [
HexagonAngle.RIGHT_UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.UP,
HexagonAngle.LEFT_UP,
HexagonAngle.LEFT_DOWN,
HexagonAngle.LEFT_DOWN,
HexagonAngle.RIGHT_DOWN, # skip
HexagonAngle.RIGHT_DOWN,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
HexagonAngle.UP,
HexagonAngle.RIGHT_UP,
]
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[6])
return points
def unit_test() -> None:
#simple_test()
#two_angle_test()
out = {}
map = out.setdefault("map", {})
map.update({
"red_segment": Point.toJson(generate_red_points()),
"back_segment": Point.toJson(generate_black_points()),
"green_segment": Point.toJson(generate_green_points()),
"blue_segment": Point.toJson(generate_blue_points()),
})
print(json.dumps(out))
# write it out to a file
with open("output.json", "w") as f:
f.write(json.dumps(out))
if __name__ == "__main__":
unit_test()

View File

@@ -0,0 +1,158 @@
/*
* Maps hex topology onto LED's
* (C) Voidstar Lab LLC 2021
*/
#ifndef MAPPING_H_
#define MAPPING_H_
// I accidentally noted these down 1-indexed and I'm too tired to adjust them
#define headof(S) ((S - 1) * 14)
#define tailof(S) (headof(S) + 13)
// Beam 0 is at 12:00 and advance clockwise
// -1 means nothing connected on that side
int nodeConnections[25][6] = {
{-1, -1, 1, -1, 0, -1},
{-1, -1, 3, -1, 2, -1},
{-1, -1, 5, -1, 4, -1},
{-1, 0, 6, 12, -1, -1},
{-1, 2, 8, 14, 7, 1},
{-1, 4, 10, 16, 9, 3},
{-1, -1, -1, 18, 11, 5},
{-1, 7, -1, 13, -1, 6},
{-1, 9, -1, 15, -1, 8},
{-1, 11, -1, 17, -1, 10},
{12, -1, 19, -1, -1, -1},
{14, -1, 21, -1, 20, -1},
{16, -1, 23, -1, 22, -1},
{18, -1, -1, -1, 24, -1},
{13, 20, 25, 29, -1, -1},
{15, 22, 27, 31, 26, 21},
{17, 24, -1, 33, 28, 23},
{-1, 26, -1, 30, -1, 25},
{-1, 28, -1, 32, -1, 27},
{29, -1, 34, -1, -1, -1},
{31, -1, 36, -1, 35, -1},
{33, -1, -1, -1, 37, -1},
{30, 35, 38, -1, -1, 34},
{32, 37, -1, -1, 39, 36},
{-1, 39, -1, -1, -1, 38}
};
// First member: Node closer to ceiling
// Second: Node closer to floor
int segmentConnections[40][2] = {
{0, 3},
{0, 4},
{1, 4},
{1, 5},
{2, 5},
{2, 6},
{3, 7},
{4, 7},
{4, 8},
{5, 8},
{5, 9},
{6, 9}, // ayy
{3, 10},
{7, 14},
{4, 11},
{8, 15},
{5, 12},
{9, 16},
{6, 13},
{10, 14},
{11, 14},
{11, 15},
{12, 15},
{12, 16},
{13, 16},
{14, 17},
{15, 17},
{15, 18},
{16, 18},
{14, 19},
{17, 22},
{15, 20},
{18, 23},
{16, 21},
{19, 22},
{20, 22},
{20, 23},
{21, 23},
{22, 24},
{23, 24}
};
// First member: Strip number
// Second: LED index closer to ceiling
// Third: LED index closer to floor
int ledAssignments[40][3] = {
{2, headof(3), tailof(3)},
{2, tailof(2), headof(2)},
{1, headof(10), tailof(10)},
{1, tailof(9), headof(9)},
{1, headof(4), tailof(4)},
{1, tailof(3), headof(3)},
{2, tailof(6), headof(6)},
{3, tailof(11), headof(11)},
{1, headof(11), tailof(11)},
{1, tailof(8), headof(8)},
{1, headof(12), tailof(12)},
{0, tailof(11), headof(11)},
{2, headof(4), tailof(4)},
{3, tailof(10), headof(10)},
{2, tailof(1), headof(1)},
{1, tailof(7), headof(7)},
{1, headof(5), tailof(5)},
{0, tailof(10), headof(10)},
{1, tailof(2), headof(2)},
{2, headof(5), tailof(5)},
{3, tailof(4), headof(4)},
{3, headof(5), tailof(5)},
{0, headof(5), tailof(5)},
{0, tailof(4), headof(4)},
{1, tailof(1), headof(1)},
{3, tailof(9), headof(9)},
{0, headof(6), tailof(6)},
{1, tailof(6), headof(6)},
{0, tailof(9), headof(9)},
{3, tailof(3), headof(3)},
{3, tailof(8), headof(8)},
{3, headof(6), tailof(6)},
{0, tailof(8), headof(8)},
{0, tailof(3), headof(3)},
{3, tailof(2), headof(2)},
{3, headof(7), tailof(7)},
{0, headof(7), tailof(7)},
{0, tailof(2), headof(2)},
{3, tailof(1), headof(1)},
{0, tailof(1), headof(1)}
};
// Border nodes are on the very edge of the network.
// Ripples fired here don't look very impressive.
int numberOfBorderNodes = 10;
int borderNodes[] = {0, 1, 2, 3, 6, 10, 13, 19, 21, 24};
// Cube nodes link three equiangular segments
// Firing ripples that always turn in one direction will draw a cube
int numberOfCubeNodes = 8;
int cubeNodes[] = {7, 8, 9, 11, 12, 17, 18};
// Firing ripples that always turn in one direction will draw a starburst
int starburstNode = 15;
#endif

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,426 @@
/*
A dot animation that travels along rails
(C) Voidstar Lab LLC 2021
*/
#ifndef RIPPLE_H_
#define RIPPLE_H_
// WARNING: These slow things down enough to affect performance. Don't turn on unless you need them!
//#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement
//#define DEBUG_RENDERING // Print debug messages about translating logical to actual position
#include "FastLED.h"
#include "mapping.h"
enum rippleState {
dead,
withinNode, // Ripple isn't drawn as it passes through a node to keep the speed consistent
travelingUpwards,
travelingDownwards
};
enum rippleBehavior {
weaksauce = 0,
feisty = 1,
angry = 2,
alwaysTurnsRight = 3,
alwaysTurnsLeft = 4
};
float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
class Ripple {
public:
Ripple(int id) : rippleId(id) {
Serial.print("Instanced ripple #");
Serial.println(rippleId);
}
rippleState state = dead;
unsigned long color;
/*
If within a node: 0 is node, 1 is direction
If traveling, 0 is segment, 1 is LED position from bottom
*/
int position[2];
// Place the Ripple in a node
void start(byte n, byte d, unsigned long c, float s, unsigned long l, byte b) {
color = c;
speed = s;
lifespan = l;
behavior = b;
birthday = millis();
pressure = 0;
state = withinNode;
position[0] = n;
position[1] = d;
justStarted = true;
Serial.print("Ripple ");
Serial.print(rippleId);
Serial.print(" starting at node ");
Serial.print(position[0]);
Serial.print(" direction ");
Serial.println(position[1]);
}
void advance(byte ledColors[40][14][3]) {
unsigned long age = millis() - birthday;
if (state == dead)
return;
pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages
// TODO: Motion of ripple is severely affected by loop speed. Make it time invariant
if (pressure < 1 && (state == travelingUpwards || state == travelingDownwards)) {
// Ripple is visible but hasn't moved - render it to avoid flickering
renderLed(ledColors, age);
}
while (pressure >= 1) {
#ifdef DEBUG_ADVANCEMENT
Serial.print("Ripple ");
Serial.print(rippleId);
Serial.println(" advancing:");
#endif
switch (state) {
case withinNode: {
if (justStarted) {
justStarted = false;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Picking direction out of node ");
Serial.print(position[0]);
Serial.print(" with agr. ");
Serial.println(behavior);
#endif
int newDirection = -1;
int sharpLeft = (position[1] + 1) % 6;
int wideLeft = (position[1] + 2) % 6;
int forward = (position[1] + 3) % 6;
int wideRight = (position[1] + 4) % 6;
int sharpRight = (position[1] + 5) % 6;
if (behavior <= 2) { // Semi-random aggressive turn mode
// The more aggressive a ripple, the tighter turns it wants to make.
// If there aren't any segments it can turn to, we need to adjust its behavior.
byte anger = behavior;
while (newDirection < 0) {
if (anger == 0) {
int forwardConnection = nodeConnections[position[0]][forward];
if (forwardConnection < 0) {
// We can't go straight ahead - we need to take a more aggressive angle
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can't go straight - picking more agr. path");
#endif
anger++;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Going forward");
#endif
newDirection = forward;
}
}
if (anger == 1) {
int leftConnection = nodeConnections[position[0]][wideLeft];
int rightConnection = nodeConnections[position[0]][wideRight];
if (leftConnection >= 0 && rightConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Turning left or right at random");
#endif
newDirection = random(2) ? wideLeft : wideRight;
}
else if (leftConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can only turn left");
#endif
newDirection = wideLeft;
}
else if (rightConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can only turn right");
#endif
newDirection = wideRight;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can't make wide turn - picking more agr. path");
#endif
anger++; // Can't take shallow turn - must become more aggressive
}
}
if (anger == 2) {
int leftConnection = nodeConnections[position[0]][sharpLeft];
int rightConnection = nodeConnections[position[0]][sharpRight];
if (leftConnection >= 0 && rightConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Turning left or right at random");
#endif
newDirection = random(2) ? sharpLeft : sharpRight;
}
else if (leftConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can only turn left");
#endif
newDirection = sharpLeft;
}
else if (rightConnection >= 0) {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can only turn right");
#endif
newDirection = sharpRight;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Can't make tight turn - picking less agr. path");
#endif
anger--; // Can't take tight turn - must become less aggressive
}
}
// Note that this can't handle some circumstances,
// like a node with segments in nothing but the 0 and 3 positions.
// Good thing we don't have any of those!
}
}
else if (behavior == alwaysTurnsRight) {
for (int i = 1; i < 6; i++) {
int possibleDirection = (position[1] + i) % 6;
if (nodeConnections[position[0]][possibleDirection] >= 0) {
newDirection = possibleDirection;
break;
}
}
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Turning as rightward as possible");
#endif
}
else if (behavior == alwaysTurnsLeft) {
for (int i = 5; i >= 1; i--) {
int possibleDirection = (position[1] + i) % 6;
if (nodeConnections[position[0]][possibleDirection] >= 0) {
newDirection = possibleDirection;
break;
}
}
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Turning as leftward as possible");
#endif
}
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Leaving node ");
Serial.print(position[0]);
Serial.print(" in direction ");
Serial.println(newDirection);
#endif
position[1] = newDirection;
}
position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on
#ifdef DEBUG_ADVANCEMENT
Serial.print(" and entering segment ");
Serial.println(position[0]);
#endif
if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node
#ifdef DEBUG_ADVANCEMENT
Serial.println(" (starting at bottom)");
#endif
state = travelingUpwards;
position[1] = 0; // Starting at bottom of segment
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.println(" (starting at top)");
#endif
state = travelingDownwards;
position[1] = 13; // Starting at top of 14-LED-long strip
}
break;
}
case travelingUpwards: {
position[1]++;
if (position[1] >= 14) {
// We've reached the top!
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Reached top of seg. ");
Serial.println(position[0]);
#endif
// Enter the new node.
int segment = position[0];
position[0] = segmentConnections[position[0]][0];
for (int i = 0; i < 6; i++) {
// Figure out from which direction the ripple is entering the node.
// Allows us to exit in an appropriately aggressive direction.
int incomingConnection = nodeConnections[position[0]][i];
if (incomingConnection == segment)
position[1] = i;
}
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Entering node ");
Serial.print(position[0]);
Serial.print(" from direction ");
Serial.println(position[1]);
#endif
state = withinNode;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Moved up to seg. ");
Serial.print(position[0]);
Serial.print(" LED ");
Serial.println(position[1]);
#endif
}
break;
}
case travelingDownwards: {
position[1]--;
if (position[1] < 0) {
// We've reached the bottom!
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Reached bottom of seg. ");
Serial.println(position[0]);
#endif
// Enter the new node.
int segment = position[0];
position[0] = segmentConnections[position[0]][1];
for (int i = 0; i < 6; i++) {
// Figure out from which direction the ripple is entering the node.
// Allows us to exit in an appropriately aggressive direction.
int incomingConnection = nodeConnections[position[0]][i];
if (incomingConnection == segment)
position[1] = i;
}
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Entering node ");
Serial.print(position[0]);
Serial.print(" from direction ");
Serial.println(position[1]);
#endif
state = withinNode;
}
else {
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Moved down to seg. ");
Serial.print(position[0]);
Serial.print(" LED ");
Serial.println(position[1]);
#endif
}
break;
}
default:
break;
}
pressure -= 1;
if (state == travelingUpwards || state == travelingDownwards) {
// Ripple is visible - render it
renderLed(ledColors, age);
}
}
#ifdef DEBUG_ADVANCEMENT
Serial.print(" Age is now ");
Serial.print(age);
Serial.print('/');
Serial.println(lifespan);
#endif
if (lifespan && age >= lifespan) {
// We dead
#ifdef DEBUG_ADVANCEMENT
Serial.println(" Lifespan is up! Ripple is dead.");
#endif
state = dead;
position[0] = position[1] = pressure = age = 0;
}
}
private:
float speed; // Each loop, ripples move this many LED's.
unsigned long lifespan; // The ripple stops after this many milliseconds
/*
0: Always goes straight ahead if possible
1: Can take 60-degree turns
2: Can take 120-degree turns
*/
byte behavior;
bool justStarted = false;
float pressure; // When Pressure reaches 1, ripple will move
unsigned long birthday; // Used to track age of ripple
static byte rippleCount; // Used to give them unique ID's
byte rippleId; // Used to identify this ripple in debug output
void renderLed(byte ledColors[40][14][3], unsigned long age) {
int strip = ledAssignments[position[0]][0];
int led = ledAssignments[position[0]][2] + position[1];
FL_UNUSED(strip);
FL_UNUSED(led);
int red = ledColors[position[0]][position[1]][0];
int green = ledColors[position[0]][position[1]][1];
int blue = ledColors[position[0]][position[1]][2];
ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red)));
ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green)));
ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue)));
#ifdef DEBUG_RENDERING
Serial.print("Rendering ripple position (");
Serial.print(position[0]);
Serial.print(',');
Serial.print(position[1]);
Serial.print(") at Strip ");
Serial.print(strip);
Serial.print(", LED ");
Serial.print(led);
Serial.print(", color 0x");
for (int i = 0; i < 3; i++) {
if (ledColors[position[0]][position[1]][i] <= 0x0F)
Serial.print('0');
Serial.print(ledColors[position[0]][position[1]][i], HEX);
}
Serial.println();
#endif
}
};
#endif

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,148 @@
/// @file ColorBoost.h
/// @brief Demo of CRGB::colorBoost() for video display on WS2812 LEDs using animated rainbow effect
/// (based on Pride2015 by Mark Kriegsman)
/// @example ColorBoost.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
// This demo shows use of CRGB::colorBoost() to boost saturation for better LED display, compared to
// normal colors and colors adjusted with gamma correction.
// The demo involves animated, ever-changing rainbows (based on Pride2015 by Mark Kriegsman).
#include "FastLED.h"
#include "fl/ease.h"
using namespace fl;
UITitle title("ColorBoost");
UIDescription description("CRGB::colorBoost() is a function that boosts the saturation of a color without decimating the color from 8 bit -> gamma -> 8 bit (leaving only 8 colors for each component). Use the dropdown menus to select different easing functions for saturation and luminance. Use legacy gfx mode (?gfx=0) for best results.");
UISlider satSlider("Saturation", 60, 0, 255, 1);
// Create dropdown with descriptive ease function names
fl::string easeOptions[] = {
"None",
"In Quad",
"Out Quad",
"In-Out Quad",
"In Cubic",
"Out Cubic",
"In-Out Cubic",
"In Sine",
"Out Sine",
"In-Out Sine"
};
UIDropdown saturationFunction("Saturation Function", easeOptions);
UIDropdown luminanceFunction("Luminance Function", easeOptions);
// Group related color boost UI elements using UIGroup template multi-argument constructor
UIGroup colorBoostControls("Color Boost", satSlider, saturationFunction, luminanceFunction);
#define DATA_PIN 2
#define LED_TYPE WS2812
#define COLOR_ORDER GRB
#define WIDTH 22
#define HEIGHT 22
#define NUM_LEDS (WIDTH * HEIGHT)
#define BRIGHTNESS 150
CRGB leds[NUM_LEDS];
// fl::ScreenMap screenmap(lut.data(), lut.size());
// fl::ScreenMap screenmap(NUM_LEDS);
fl::XYMap xyMap =
fl::XYMap::constructRectangularGrid(WIDTH, HEIGHT);
void setup() {
// tell FastLED about the LED strip configuration
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(xyMap);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
// Set default dropdown selections
saturationFunction.setSelectedIndex(1); // "In Quad"
luminanceFunction.setSelectedIndex(0); // "None"
}
EaseType getEaseType(int value) {
switch (value) {
case 0: return EASE_NONE;
case 1: return EASE_IN_QUAD;
case 2: return EASE_OUT_QUAD;
case 3: return EASE_IN_OUT_QUAD;
case 4: return EASE_IN_CUBIC;
case 5: return EASE_OUT_CUBIC;
case 6: return EASE_IN_OUT_CUBIC;
case 7: return EASE_IN_SINE;
case 8: return EASE_OUT_SINE;
case 9: return EASE_IN_OUT_SINE;
}
FL_ASSERT(false, "Invalid ease type");
return EASE_NONE;
}
// Animated rainbow wave effect (Pride2015), with matrix divided into three segments to compare:
// - Normal colors (top)
// - Colors optimized using colorBoost() (middle)
// - Colors adjusted using gamma correction (bottom)
void rainbowWave() {
// Use millis() for consistent timing across different devices
// Scale down millis() to get appropriate animation speed
uint16_t time = millis() / 16; // Adjust divisor to control wave speed
uint8_t hueOffset = millis() / 32; // Adjust divisor to control hue rotation speed
// Iterate through the entire matrix
for (uint16_t y = 0; y < HEIGHT; y++) {
for (uint16_t x = 0; x < WIDTH; x++) {
// Create a wave pattern using sine function based on position and time
uint8_t wave = sin8(time + (x * 8));
// Calculate hue based on position and time for rainbow effect
uint8_t hue = hueOffset + (x * 255 / WIDTH);
// Use wave for both saturation and brightness variation
// uint8_t sat = 255 - (wave / 4); // Subtle saturation variation
uint8_t bri = 128 + (wave / 2); // Brightness wave from 128 to 255
// Create the original color using HSV
CRGB original_color = CHSV(hue, satSlider.value(), bri);
if (y > HEIGHT / 3 * 2) {
// Upper third - original colors
leds[xyMap(x, y)] = original_color;
} else if (y > HEIGHT / 3) {
// Middle third - colors transformed with colorBoost()
EaseType sat_ease = getEaseType(saturationFunction.as_int());
EaseType lum_ease = getEaseType(luminanceFunction.as_int());
leds[xyMap(x, y)] = original_color.colorBoost(sat_ease, lum_ease);
} else {
// Lower third - colors transformed using gamma correction
float r = original_color.r / 255.f;
float g = original_color.g / 255.f;
float b = original_color.b / 255.f;
r = pow(r, 2.0);
g = pow(g, 2.0);
b = pow(b, 2.0);
r = r * 255.f;
g = g * 255.f;
b = b * 255.f;
leds[xyMap(x, y)] = CRGB(r, g, b);
}
}
}
}
void loop() {
rainbowWave();
FastLED.show();
}

View File

@@ -0,0 +1,18 @@
#include "fl/sketch_macros.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "./ColorBoost.h"
#else
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println("Not enough memory");
delay(1000);
}
#endif

View File

@@ -0,0 +1,203 @@
/// @file ColorPalette.ino
/// @brief Demonstrates how to use @ref ColorPalettes
/// @example ColorPalette.ino
#include <FastLED.h>
#define LED_PIN 5
#define NUM_LEDS 50
#define BRIGHTNESS 64
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define UPDATES_PER_SECOND 100
// This example shows several ways to set up and use 'palettes' of colors
// with FastLED.
//
// These compact palettes provide an easy way to re-colorize your
// animation on the fly, quickly, easily, and with low overhead.
//
// USING palettes is MUCH simpler in practice than in theory, so first just
// run this sketch, and watch the pretty lights as you then read through
// the code. Although this sketch has eight (or more) different color schemes,
// the entire sketch compiles down to about 6.5K on AVR.
//
// FastLED provides a few pre-configured color palettes, and makes it
// extremely easy to make up your own color schemes with palettes.
//
// Some notes on the more abstract 'theory and practice' of
// FastLED compact palettes are at the bottom of this file.
CRGBPalette16 currentPalette;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p FL_PROGMEM;
// If you are using the fastled compiler, then you must declare your functions
// before you use them. This is standard in C++ and C projects, but ino's are
// special in that they do this for you. Eventually we will try to emulate this
// feature ourselves but in the meantime you'll have to declare your functions
// before you use them if you want to use our compiler.
void ChangePalettePeriodically();
void FillLEDsFromPaletteColors(uint8_t colorIndex);
void SetupPurpleAndGreenPalette();
void SetupTotallyRandomPalette();
void SetupBlackAndWhiteStripedPalette();
void setup() {
delay( 3000 ); // power-up safety delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness( BRIGHTNESS );
currentPalette = RainbowColors_p;
currentBlending = LINEARBLEND;
}
void loop()
{
ChangePalettePeriodically();
static uint8_t startIndex = 0;
startIndex = startIndex + 1; /* motion speed */
FillLEDsFromPaletteColors( startIndex);
FastLED.show();
FastLED.delay(1000 / UPDATES_PER_SECOND);
}
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
uint8_t brightness = 255;
for( int i = 0; i < NUM_LEDS; ++i) {
leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
colorIndex += 3;
}
}
// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly. All are shown here.
void ChangePalettePeriodically()
{
uint8_t secondHand = (millis() / 1000) % 60;
static uint8_t lastSecond = 99;
if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; }
if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; }
if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; }
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
}
}
// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
for( int i = 0; i < 16; ++i) {
currentPalette[i] = CHSV( random8(), 255, random8());
}
}
// This function sets up a palette of black and white stripes,
// using code. Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;
}
// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green = CHSV( HUE_GREEN, 255, 255);
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
green, green, black, black,
purple, purple, black, black,
green, green, black, black,
purple, purple, black, black );
}
// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM. A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p FL_PROGMEM =
{
CRGB::Red,
CRGB::Gray, // 'white' is too bright compared to red and blue
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Gray,
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Red,
CRGB::Gray,
CRGB::Gray,
CRGB::Blue,
CRGB::Blue,
CRGB::Black,
CRGB::Black
};
// Additional notes on FastLED compact palettes:
//
// Normally, in computer graphics, the palette (or "color lookup table")
// has 256 entries, each containing a specific 24-bit RGB color. You can then
// index into the color palette using a simple 8-bit (one byte) value.
// A 256-entry color palette takes up 768 bytes of RAM, which on Arduino
// is quite possibly "too many" bytes.
//
// FastLED does offer traditional 256-element palettes, for setups that
// can afford the 768-byte cost in RAM.
//
// However, FastLED also offers a compact alternative. FastLED offers
// palettes that store 16 distinct entries, but can be accessed AS IF
// they actually have 256 entries; this is accomplished by interpolating
// between the 16 explicit entries to create fifteen intermediate palette
// entries between each pair.
//
// So for example, if you set the first two explicit entries of a compact
// palette to Green (0,255,0) and Blue (0,0,255), and then retrieved
// the first sixteen entries from the virtual palette (of 256), you'd get
// Green, followed by a smooth gradient from green-to-blue, and then Blue.

View File

@@ -0,0 +1,89 @@
/// @file ColorTemperature.ino
/// @brief Demonstrates how to use @ref ColorTemperature based color correction
/// @example ColorTemperature.ino
#include <FastLED.h>
#define LED_PIN 3
// Information about the LED strip itself
#define NUM_LEDS 60
#define CHIPSET WS2811
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 128
// FastLED provides two color-management controls:
// (1) color correction settings for each LED strip, and
// (2) master control of the overall output 'color temperature'
//
// THIS EXAMPLE demonstrates the second, "color temperature" control.
// It shows a simple rainbow animation first with one temperature profile,
// and a few seconds later, with a different temperature profile.
//
// The first pixel of the strip will show the color temperature.
//
// HELPFUL HINTS for "seeing" the effect in this demo:
// * Don't look directly at the LED pixels. Shine the LEDs aganst
// a white wall, table, or piece of paper, and look at the reflected light.
//
// * If you watch it for a bit, and then walk away, and then come back
// to it, you'll probably be able to "see" whether it's currently using
// the 'redder' or the 'bluer' temperature profile, even not counting
// the lowest 'indicator' pixel.
//
//
// FastLED provides these pre-conigured incandescent color profiles:
// Candle, Tungsten40W, Tungsten100W, Halogen, CarbonArc,
// HighNoonSun, DirectSunlight, OvercastSky, ClearBlueSky,
// FastLED provides these pre-configured gaseous-light color profiles:
// WarmFluorescent, StandardFluorescent, CoolWhiteFluorescent,
// FullSpectrumFluorescent, GrowLightFluorescent, BlackLightFluorescent,
// MercuryVapor, SodiumVapor, MetalHalide, HighPressureSodium,
// FastLED also provides an "Uncorrected temperature" profile
// UncorrectedTemperature;
#define TEMPERATURE_1 Tungsten100W
#define TEMPERATURE_2 OvercastSky
// How many seconds to show each temperature before switching
#define DISPLAYTIME 20
// How many seconds to show black between switches
#define BLACKTIME 3
void loop()
{
// draw a generic, no-name rainbow
static uint8_t starthue = 0;
fill_rainbow( leds + 5, NUM_LEDS - 5, --starthue, 20);
// Choose which 'color temperature' profile to enable.
uint8_t secs = (millis() / 1000) % (DISPLAYTIME * 2);
if( secs < DISPLAYTIME) {
FastLED.setTemperature( TEMPERATURE_1 ); // first temperature
leds[0] = TEMPERATURE_1; // show indicator pixel
} else {
FastLED.setTemperature( TEMPERATURE_2 ); // second temperature
leds[0] = TEMPERATURE_2; // show indicator pixel
}
// Black out the LEDs for a few secnds between color changes
// to let the eyes and brains adjust
if( (secs % DISPLAYTIME) < BLACKTIME) {
memset8( leds, 0, NUM_LEDS * sizeof(CRGB));
}
FastLED.show();
FastLED.delay(8);
}
void setup() {
delay( 3000 ); // power-up safety delay
// It's important to set the color correction for your LED strip here,
// so that colors can be more accurately rendered through the 'temperature' profiles
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalSMD5050 );
FastLED.setBrightness( BRIGHTNESS );
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "FastLED.h"
void apollo3_tests() {
#if FASTLED_USE_PROGMEM != 0
#error "FASTLED_USE_PROGMEM should be 0 for Apollo3"
#endif
#if SKETCH_HAS_LOTS_OF_MEMORY != 1
#error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for Apollo3"
#endif
#if FASTLED_ALLOW_INTERRUPTS != 1
#error "FASTLED_ALLOW_INTERRUPTS should be 1 for Apollo3"
#endif
#ifndef F_CPU
#error "F_CPU should be defined for Apollo3"
#endif
// Check that Apollo3-specific features are available
#ifndef APOLLO3
#warning "APOLLO3 macro not defined - this may indicate platform detection issues"
#endif
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include "FastLED.h"
void arm_tests() {
#ifndef FASTLED_ARM
#error "FASTLED_ARM should be defined for ARM platforms"
#endif
#if FASTLED_USE_PROGMEM != 0 && FASTLED_USE_PROGMEM != 1
#error "FASTLED_USE_PROGMEM should be either 0 or 1 for ARM platforms"
#endif
#if defined(ARDUINO_TEENSYLC) || defined(ARDUINO_TEENSY30) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(ARDUINO_ARCH_RENESAS_UNO) || defined(STM32F1)
// Teensy LC, Teensy 3.0, Teensy 3.1/3.2, Renesas UNO, and STM32F1 have limited memory
#if SKETCH_HAS_LOTS_OF_MEMORY != 0
#error "SKETCH_HAS_LOTS_OF_MEMORY should be 0 for Teensy LC, Teensy 3.0, Teensy 3.1/3.2, Renesas UNO, and STM32F1"
#endif
#else
// Most other ARM platforms have lots of memory
#if SKETCH_HAS_LOTS_OF_MEMORY != 1
#error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for most ARM platforms"
#endif
#endif
#if FASTLED_ALLOW_INTERRUPTS != 1 && FASTLED_ALLOW_INTERRUPTS != 0
#error "FASTLED_ALLOW_INTERRUPTS should be either 0 or 1 for ARM platforms"
#endif
// Check that F_CPU is defined
#ifndef F_CPU
#error "F_CPU should be defined for ARM platforms"
#endif
// Specific ARM variant checks
#if defined(ARDUINO_ARCH_STM32) || defined(STM32F1)
#if FASTLED_ALLOW_INTERRUPTS != 0
#error "STM32 platforms should have FASTLED_ALLOW_INTERRUPTS set to 0"
#endif
#if FASTLED_USE_PROGMEM != 0
#error "STM32 platforms should have FASTLED_USE_PROGMEM set to 0"
#endif
#endif
#if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_RASPBERRY_PI_PICO)
#if FASTLED_USE_PROGMEM != 0
#error "RP2040 platforms should have FASTLED_USE_PROGMEM set to 0"
#endif
#if FASTLED_ALLOW_INTERRUPTS != 1
#error "RP2040 platforms should have FASTLED_ALLOW_INTERRUPTS set to 1"
#endif
#ifdef FASTLED_FORCE_SOFTWARE_SPI
// RP2040 forces software SPI - this is expected
#endif
#endif
#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__)
// Teensy platforms that use PROGMEM
#if FASTLED_USE_PROGMEM != 1
#error "Teensy K20/K66/MXRT1062 platforms should have FASTLED_USE_PROGMEM set to 1"
#endif
#endif
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAM_DUE)
#if FASTLED_USE_PROGMEM != 0
#error "SAMD/SAM platforms should have FASTLED_USE_PROGMEM set to 0"
#endif
#endif
#if defined(NRF52_SERIES) || defined(ARDUINO_ARCH_NRF52)
#if FASTLED_USE_PROGMEM != 0
#error "NRF52 platforms should have FASTLED_USE_PROGMEM set to 0"
#endif
#ifndef CLOCKLESS_FREQUENCY
#error "NRF52 should have CLOCKLESS_FREQUENCY defined"
#endif
#endif
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "FastLED.h"
void avr_tests() {
#if FASTLED_USE_PROGMEM != 1
#error "FASTLED_USE_PROGMEM should be 1 for AVR"
#endif
#if SKETCH_HAS_LOTS_OF_MEMORY != 0
#error "SKETCH_HAS_LOTS_OF_MEMORY should be 0 for AVR"
#endif
#if FASTLED_ALLOW_INTERRUPTS != 0
#error "FASTLED_ALLOW_INTERRUPTS should be 0 for AVR (default)"
#endif
#ifndef FASTLED_AVR
#error "FASTLED_AVR should be defined for AVR platforms"
#endif
#ifndef F_CPU
#error "F_CPU should be defined for AVR platforms"
#endif
// AVR should have a reasonable F_CPU (typically 8MHz or 16MHz)
#if F_CPU < 1000000 || F_CPU > 32000000
#warning "AVR F_CPU seems unusual - check if this is expected"
#endif
// Check for Arduino environment on AVR
#ifndef ARDUINO
#warning "ARDUINO macro not defined - may not be in Arduino environment"
#endif
// AVR-specific assembly optimizations should be enabled
#ifndef QADD8_AVRASM
#warning "AVR assembly optimizations may not be enabled"
#endif
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include "FastLED.h"
void wasm_tests() {
#if FASTLED_USE_PROGMEM != 0
#error "FASTLED_USE_PROGMEM should be 0 for WASM"
#endif
#if SKETCH_HAS_LOTS_OF_MEMORY != 1
#error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for WASM"
#endif
#if FASTLED_ALLOW_INTERRUPTS != 1
#error "FASTLED_ALLOW_INTERRUPTS should be 1 for WASM"
#endif
#ifndef F_CPU
#error "F_CPU should be defined for WASM"
#endif
// WASM should have a high F_CPU since it's not real hardware
#if F_CPU < 1000000
#error "WASM F_CPU should be reasonably high (not real hardware constraint)"
#endif
// Check WASM-specific defines
#ifndef FASTLED_STUB_IMPL
#error "FASTLED_STUB_IMPL should be defined for WASM"
#endif
#ifndef digitalPinToBitMask
#error "digitalPinToBitMask should be defined for WASM"
#endif
}

View File

@@ -0,0 +1,183 @@
/*
Basic cork screw test.
This test is forward mapping, in which we test that
the corkscrew is mapped to cylinder cartesian coordinates.
Most of the time, you'll want the reverse mapping, that is
drawing to a rectangular grid, and then mapping that to a corkscrew.
However, to make sure the above mapping works correctly, we have
to test that the forward mapping works correctly first.
NEW: ScreenMap Support
=====================
You can now create a ScreenMap directly from a Corkscrew, which maps
each LED index to its exact position on the cylindrical surface.
This is useful for web interfaces and visualization:
Example usage:
```cpp
// Create a corkscrew
Corkscrew corkscrew(totalTurns, numLeds);
// Create ScreenMap with 0.5cm LED diameter
fl::ScreenMap screenMap = corkscrew.toScreenMap(0.5f);
// Use with FastLED controller for web visualization
controller->setScreenMap(screenMap);
```
NEW: Rectangular Buffer Support
===============================
You can now draw into a rectangular fl::Leds grid and read that
into the corkscrew's internal buffer for display:
Example usage:
```cpp
// Create a rectangular grid to draw into
CRGB grid_buffer[width * height];
fl::Leds grid(grid_buffer, width, height);
// Draw your 2D patterns into the grid
grid(x, y) = CRGB::Red; // etc.
// Draw patterns on the corkscrew's surface and map to LEDs
auto& surface = corkscrew.surface();
// Draw your patterns on the surface, then call draw() to map to LEDs
corkscrew.draw();
// Access the LED pixel data
auto ledData = corkscrew.data(); // Or corkscrew.rawData() for pointer
// The LED data now contains the corkscrew mapping of your patterns
```
*/
#include "fl/assert.h"
#include "fl/corkscrew.h"
#include "fl/grid.h"
#include "fl/leds.h"
#include "fl/screenmap.h"
#include "fl/sstream.h"
#include "fl/warn.h"
#include "noise.h"
#include <FastLED.h>
// #include "vec3.h"
using namespace fl;
#define PIN_DATA 9
#define NUM_LEDS 288
#define CORKSCREW_TURNS 19 // Default to 19 turns
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
UITitle festivalStickTitle("Corkscrew");
UIDescription festivalStickDescription(
"Tests the ability to map a cork screw onto a 2D cylindrical surface");
UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
UICheckbox allWhite("All White", false);
UICheckbox splatRendering("Splat Rendering", true);
UICheckbox cachingEnabled("Enable Tile Caching", true);
// CRGB leds[NUM_LEDS];
// Tested on a 288 led (2x 144 max density led strip) with 19 turns
// Auto-calculates optimal grid dimensions from turns and LEDs
Corkscrew corkscrew(CORKSCREW_TURNS, // 19 turns
NUM_LEDS); // 288 leds
// Create a corkscrew with:
// - 30cm total length (300mm)
// - 5cm width (50mm)
// - 2mm LED inner diameter
// - 24 LEDs per turn
// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS,
// 300.0f, 50.0f, 2.0f, 24.0f);
// fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
fl::ScreenMap screenMap;
fl::Grid<CRGB> frameBuffer;
void setup() {
int width = corkscrew.cylinderWidth();
int height = corkscrew.cylinderHeight();
frameBuffer.reset(width, height);
XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0);
CRGB *leds = frameBuffer.data();
size_t num_leds = frameBuffer.size();
CLEDController *controller =
&FastLED.addLeds<WS2812, 3, BGR>(leds, num_leds);
// NEW: Create ScreenMap directly from Corkscrew using toScreenMap()
// This maps each LED index to its exact position on the cylindrical surface
fl::ScreenMap corkscrewScreenMap = corkscrew.toScreenMap(0.2f);
// Alternative: Create ScreenMap from rectangular XYMap (old way)
// fl::ScreenMap screenMap = xyMap.toScreenMap();
// screenMap.setDiameter(.2f);
// Set the corkscrew screen map for the controller
// This allows the web interface to display the actual corkscrew shape
controller->setScreenMap(corkscrewScreenMap);
// Initialize caching based on UI setting
corkscrew.setCachingEnabled(cachingEnabled.value());
}
void loop() {
fl::clear(frameBuffer);
static float pos = 0;
pos += speed.value();
if (pos > corkscrew.size() - 1) {
pos = 0; // Reset to the beginning
}
// Update caching setting if it changed
static bool lastCachingState = cachingEnabled.value();
if (lastCachingState != cachingEnabled.value()) {
corkscrew.setCachingEnabled(cachingEnabled.value());
lastCachingState = cachingEnabled.value();
}
if (allWhite) {
for (size_t i = 0; i < frameBuffer.size(); ++i) {
frameBuffer.data()[i] = CRGB(8, 8, 8);
}
}
if (splatRendering) {
Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos);
const CRGB color = CRGB::Blue;
// Draw each pixel in the 2x2 tile using the new wrapping API
for (int dx = 0; dx < 2; ++dx) {
for (int dy = 0; dy < 2; ++dy) {
auto data = pos_tile.at(dx, dy);
vec2<u16> wrapped_pos = data.first; // Already wrapped position
uint8_t alpha = data.second; // Alpha value
if (alpha > 0) { // Only draw if there's some alpha
CRGB c = color;
c.nscale8(alpha); // Scale the color by the alpha value
frameBuffer.at(wrapped_pos.x, wrapped_pos.y) = c;
}
}
}
} else {
// None splat rendering, looks aweful.
vec2f pos_vec2f = corkscrew.at_no_wrap(pos);
vec2<u16> pos_i16 = vec2<u16>(round(pos_vec2f.x), round(pos_vec2f.y));
// Now map the cork screw position to the cylindrical buffer that we
// will draw.
frameBuffer.at(pos_i16.x, pos_i16.y) =
CRGB::Blue; // Draw a blue pixel at (w, h)
}
FastLED.show();
}

View File

@@ -0,0 +1,11 @@
#include <FastLED.h> // Main FastLED library for controlling LEDs
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically
// don't have enough memory to handle this complex animation.
// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
void setup() {}
void loop() {}
#else // For all other platforms with more memory (ESP32, Teensy, etc.)
#include "Corkscrew.h"
#endif // End of the non-AVR code section

View File

@@ -0,0 +1,60 @@
/// @file Cylon.ino
/// @brief An animation that moves a single LED back and forth as the entire strip changes.
/// (Larson Scanner effect)
/// @example Cylon.ino
#include <FastLED.h>
using namespace fl;
// How many leds in your strip?
#define NUM_LEDS 64
// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806, define both DATA_PIN and CLOCK_PIN
#define DATA_PIN 2
#define CLOCK_PIN 13
// Define the array of leds
CRGB leds[NUM_LEDS];
void setup() {
Serial.begin(57600);
Serial.println("resetting");
FastLED.addLeds<WS2812,DATA_PIN,RGB>(leds,NUM_LEDS);
FastLED.setBrightness(84);
}
void fadeall() { for(int i = 0; i < NUM_LEDS; i++) { leds[i].nscale8(250); } }
void loop() {
static uint8_t hue = 0;
Serial.print("x");
// First slide the led in one direction
for(int i = 0; i < NUM_LEDS; i++) {
// Set the i'th led to red
leds[i] = CHSV(hue++, 255, 255);
// Show the leds
FastLED.show();
// now that we've shown the leds, reset the i'th led to black
// leds[i] = CRGB::Black;
fadeall();
// Wait a little bit before we loop around and do it again
delay(10);
}
Serial.print("x");
// Now go in the other direction.
for(int i = (NUM_LEDS)-1; i >= 0; i--) {
// Set the i'th led to red
leds[i] = CHSV(hue++, 255, 255);
// Show the leds
FastLED.show();
// now that we've shown the leds, reset the i'th led to black
// leds[i] = CRGB::Black;
fadeall();
// Wait a little bit before we loop around and do it again
delay(10);
}
}

View File

@@ -0,0 +1,133 @@
/// @file DemoReel100.ino
/// @brief FastLED "100 lines of code" demo reel, showing off some effects
/// @example DemoReel100.ino
#include <FastLED.h>
// FastLED "100-lines-of-code" demo reel, showing just a few
// of the kinds of animation patterns you can quickly and easily
// compose using FastLED.
//
// This example also shows one easy way to define multiple
// animations patterns and have them automatically rotate.
//
// -Mark Kriegsman, December 2014
#define DATA_PIN 3
//#define CLK_PIN 4
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define NUM_LEDS 64
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 96
#define FRAMES_PER_SECOND 120
void setup() {
delay(3000); // 3 second delay for recovery
// tell FastLED about the LED strip configuration
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
//FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
// Forward declarations for pattern functions
void rainbow();
void rainbowWithGlitter();
void confetti();
void sinelon();
void juggle();
void bpm();
void nextPattern();
void addGlitter(fract8 chanceOfGlitter);
// List of patterns to cycle through. Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm };
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns
void loop()
{
// Call the current pattern function once, updating the 'leds' array
gPatterns[gCurrentPatternNumber]();
// send the 'leds' array out to the actual LED strip
FastLED.show();
// insert a delay to keep the framerate modest
FastLED.delay(1000/FRAMES_PER_SECOND);
// do some periodic updates
EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow
EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically
}
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
void nextPattern()
{
// add one to the current pattern number, and wrap around at the end
gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}
void rainbow()
{
// FastLED's built-in rainbow generator
fill_rainbow( leds, NUM_LEDS, gHue, 7);
}
void rainbowWithGlitter()
{
// built-in FastLED rainbow, plus some random sparkly glitter
rainbow();
addGlitter(80);
}
void addGlitter( fract8 chanceOfGlitter)
{
if( random8() < chanceOfGlitter) {
leds[ random16(NUM_LEDS) ] += CRGB::White;
}
}
void confetti()
{
// random colored speckles that blink in and fade smoothly
fadeToBlackBy( leds, NUM_LEDS, 10);
int pos = random16(NUM_LEDS);
leds[pos] += CHSV( gHue + random8(64), 200, 255);
}
void sinelon()
{
// a colored dot sweeping back and forth, with fading trails
fadeToBlackBy( leds, NUM_LEDS, 20);
int pos = beatsin16( 13, 0, NUM_LEDS-1 );
leds[pos] += CHSV( gHue, 255, 192);
}
void bpm()
{
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint8_t BeatsPerMinute = 62;
CRGBPalette16 palette = PartyColors_p;
uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
for( int i = 0; i < NUM_LEDS; i++) { //9948
leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
}
}
void juggle() {
// eight colored dots, weaving in and out of sync with each other
fadeToBlackBy( leds, NUM_LEDS, 20);
uint8_t dothue = 0;
for( int i = 0; i < 8; i++) {
leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255);
dothue += 32;
}
}

View File

@@ -0,0 +1,218 @@
/*
This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled
Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable
all the UI elements you see below.
*/
#include <Arduino.h>
#include <FastLED.h>
#include "fl/downscale.h"
#include "fl/draw_visitor.h"
#include "fl/math_macros.h"
#include "fl/raster.h"
#include "fl/time_alpha.h"
#include "fl/ui.h"
#include "fl/xypath.h"
#include "fx/time.h"
// Sketch.
#include "src/wave.h"
#include "src/xypaths.h"
using namespace fl;
#define HEIGHT 64
#define WIDTH 64
#define NUM_LEDS ((WIDTH) * (HEIGHT))
#define TIME_ANIMATION 1000 // ms
CRGB leds[NUM_LEDS];
CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer
XYMap xyMap(WIDTH, HEIGHT, false);
XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2,
false); // Framebuffer is regular rectangle LED matrix.
// XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT);
// Speed up writing to the super sampled waveFx by writing
// to a raster. This will allow duplicate writes to be removed.
WaveEffect wave_fx; // init in setup().
fl::vector<XYPathPtr> shapes = CreateXYPaths(WIDTH, HEIGHT);
XYRaster raster(WIDTH, HEIGHT);
TimeWarp time_warp;
XYPathPtr getShape(int which) {
int len = shapes.size();
which = which % len;
if (which < 0) {
which += len;
}
return shapes[which];
}
//////////////////// UI Section /////////////////////////////
UITitle title("XYPath Demo");
UIDescription description("Use a path on the WaveFx");
UIButton trigger("Trigger");
UISlider whichShape("Which Shape", 0.0f, 0.0f, shapes.size() - 1, 1.0f);
UICheckbox useWaveFx("Use WaveFX", true);
UISlider transition("Transition", 0.0f, 0.0f, 1.0f, 0.01f);
UISlider scale("Scale", 1.0f, 0.0f, 1.0f, 0.01f);
UISlider speed("Speed", 1.0f, -20.0f, 20.0f, 0.01f);
UISlider numberOfSteps("Number of Steps", 32.0f, 1.0f, 100.0f, 1.0f);
UISlider maxAnimation("Max Animation", 1.0f, 5.0f, 20.0f, 1.f);
TimeClampedTransition shapeProgress(TIME_ANIMATION);
void setupUiCallbacks() {
speed.onChanged([](UISlider& slider) {
time_warp.setSpeed(slider.value());
});
maxAnimation.onChanged(
[](UISlider& slider) {
shapeProgress.set_max_clamp(slider.value());
});
trigger.onClicked([]() {
// shapeProgress.trigger(millis());
FASTLED_WARN("Trigger pressed");
});
useWaveFx.onChanged([](fl::UICheckbox &checkbox) {
if (checkbox.value()) {
FASTLED_WARN("WaveFX enabled");
} else {
FASTLED_WARN("WaveFX disabled");
}
});
}
void setup() {
Serial.begin(115200);
auto screenmap = xyMap.toScreenMap();
screenmap.setDiameter(.2);
FastLED.addLeds<NEOPIXEL, 2>(leds, xyMap.getTotal())
.setScreenMap(screenmap);
auto screenmap2 = xyMap_Dst.toScreenMap();
screenmap.setDiameter(.5);
screenmap2.addOffsetY(-HEIGHT / 2);
FastLED.addLeds<NEOPIXEL, 3>(leds_downscaled, xyMap_Dst.getTotal())
.setScreenMap(screenmap2);
setupUiCallbacks();
// Initialize wave simulation. Please don't use static constructors, keep it
// in setup().
trigger.click();
wave_fx = NewWaveSimulation2D(xyMap);
}
//////////////////// LOOP SECTION /////////////////////////////
float getAnimationTime(uint32_t now) {
float pointf = shapeProgress.updatef(now);
return pointf + transition.value();
}
void clearLeds() {
fl::clear(leds);
fl::clear(leds_downscaled);
};
void loop() {
// Your code here
clearLeds();
const uint32_t now = millis();
uint32_t now_warped = time_warp.update(now);
auto shape = getShape(whichShape.as<int>());
shape->setScale(scale.value());
float curr_alpha = getAnimationTime(now_warped);
static float s_prev_alpha = 0.0f;
// unconditionally apply the circle.
if (trigger) {
// trigger the transition
time_warp.reset(now);
now_warped = time_warp.update(now);
shapeProgress.trigger(now_warped);
FASTLED_WARN("Transition triggered on " << shape->name());
curr_alpha = getAnimationTime(now_warped);
s_prev_alpha = curr_alpha;
}
clearLeds();
const CRGB purple = CRGB(255, 0, 255);
const int number_of_steps = numberOfSteps.value();
raster.reset();
float diff = curr_alpha - s_prev_alpha;
diff *= 1.0f;
float factor = MAX(s_prev_alpha - diff, 0.f);
for (int i = 0; i < number_of_steps; ++i) {
float a =
fl::map_range<float>(i, 0, number_of_steps - 1, factor, curr_alpha);
if (a < .04) {
// shorter tails at first.
a = map_range<float>(a, 0.0f, .04f, 0.0f, .04f);
}
float diff_max_alpha = maxAnimation.value() - curr_alpha;
if (diff_max_alpha < 0.94) {
// shorter tails at the end.
a = map_range<float>(a, curr_alpha, maxAnimation.value(),
curr_alpha, maxAnimation.value());
}
uint8_t alpha =
fl::map_range<uint8_t>(i, 0.0f, number_of_steps - 1, 64, 255);
Tile2x2_u8 subpixel = shape->at_subpixel(a);
subpixel.scale(alpha);
// subpixels.push_back(subpixel);
raster.rasterize(subpixel);
}
s_prev_alpha = curr_alpha;
if (useWaveFx) {
DrawRasterToWaveSimulator draw_wave_fx(&wave_fx);
raster.draw(xyMap, draw_wave_fx);
} else {
raster.draw(purple, xyMap, leds);
}
int first = xyMap(1, 1);
int last = xyMap(WIDTH - 2, HEIGHT - 2);
leds[first] = CRGB(255, 0, 0);
leds[last] = CRGB(0, 255, 0);
if (useWaveFx) {
// fxBlend.draw(Fx::DrawContext(now, leds));
wave_fx.draw(Fx::DrawContext(now, leds));
}
// downscaleBilinear(leds, WIDTH, HEIGHT, leds_downscaled, WIDTH / 2,
// HEIGHT / 2);
downscaleHalf(leds, xyMap, leds_downscaled, xyMap_Dst);
// Print out the first 10 pixels of the original and downscaled
fl::vector_inlined<CRGB, 10> downscaled_pixels;
fl::vector_inlined<CRGB, 10> original_pixels;
for (int i = 0; i < 10; ++i) {
original_pixels.push_back(leds[i]);
downscaled_pixels.push_back(leds_downscaled[i]);
}
FastLED.show();
}

View File

@@ -0,0 +1,18 @@
#include <Arduino.h>
#include <FastLED.h>
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {
Serial.println("setup");
}
void loop() {
Serial.println("Downscale disabled");
delay(1000);
}
#else
#include "Downscale.h"
#endif // SKETCH_HAS_LOTS_OF_MEMORY

View File

@@ -0,0 +1,65 @@
#include "wave.h"
#include "FastLED.h"
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
0, 0, 0, 0, // Black
32, 0, 0, 70, // Dark blue
128, 20, 57, 255, // Electric blue
255, 255, 255, 255 // White
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
0, 0, 0, 0, // black
8, 128, 64, 64, // green
16, 255, 222, 222, // red
64, 255, 255, 255, // white
255, 255, 255, 255 // white
};
WaveFx::Args CreateArgsLower() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X;
out.half_duplex = true;
out.auto_updates = true;
out.speed = 0.18f;
out.dampening = 9.0f;
out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricBlueFirePal);
return out;
}
WaveFx::Args CreateArgsUpper() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X;
out.half_duplex = true;
out.auto_updates = true;
out.speed = 0.25f;
out.dampening = 3.0f;
out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricGreenFirePal);
return out;
}
WaveEffect NewWaveSimulation2D(const XYMap& xymap) {
// only apply complex xymap as the last step after compositiing.
XYMap xy_rect =
XYMap::constructRectangularGrid(xymap.getWidth(), xymap.getHeight());
Blend2dPtr fxBlend =
fl::make_shared<Blend2d>(xymap); // Final transformation goes to the blend stack.
int width = xymap.getWidth();
int height = xymap.getHeight();
XYMap xyRect(width, height, false);
WaveFx::Args args_lower = CreateArgsLower();
WaveFx::Args args_upper = CreateArgsUpper();
WaveFxPtr wave_fx_low = fl::make_shared<WaveFx>(xy_rect, args_lower);
WaveFxPtr wave_fx_high = fl::make_shared<WaveFx>(xy_rect, args_upper);
Blend2dPtr blend_stack = fl::make_shared<Blend2d>(xymap);
blend_stack->add(wave_fx_low);
blend_stack->add(wave_fx_high);
WaveEffect out = {
.wave_fx_low = wave_fx_low,
.wave_fx_high = wave_fx_high,
.blend_stack = blend_stack,
};
return out;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "fx/2d/blend.h"
#include "fx/2d/wave.h"
#include "fx/fx2d.h"
#include "fl/raster.h"
using namespace fl;
struct WaveEffect {
WaveFxPtr wave_fx_low;
WaveFxPtr wave_fx_high;
Blend2dPtr blend_stack;
void draw(Fx::DrawContext context) { blend_stack->draw(context); }
void addf(size_t x, size_t y, float value) {
wave_fx_low->addf(x, y, value);
wave_fx_high->addf(x, y, value);
}
};
struct DrawRasterToWaveSimulator {
DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {}
void draw(const vec2<uint16_t> &pt, uint32_t /*index*/, uint8_t value) {
float valuef = value / 255.0f;
size_t xx = pt.x;
size_t yy = pt.y;
mWaveFx->addf(xx, yy, valuef);
}
WaveEffect* mWaveFx;
};
WaveEffect NewWaveSimulation2D(const XYMap& xymap);

View File

@@ -0,0 +1,41 @@
#include "fl/xypath.h"
#include "fl/vector.h"
#include "fl/map_range.h"
#include "xypaths.h"
using namespace fl;
namespace {
fl::shared_ptr<CatmullRomParams> make_path(int width, int height) {
// make a triangle.
fl::shared_ptr<CatmullRomParams> params = fl::make_shared<CatmullRomParams>();
vector_inlined<vec2f, 5> points;
points.push_back(vec2f(0.0f, 0.0f));
points.push_back(vec2f(width / 3, height / 2));
points.push_back(vec2f(width - 3, height - 1));
points.push_back(vec2f(0.0f, height - 1));
points.push_back(vec2f(0.0f, 0.0f));
for (auto &p : points) {
p.x = map_range<float, float>(p.x, 0.0f, width - 1, -1.0f, 1.0f);
p.y = map_range<float, float>(p.y, 0.0f, height - 1, -1.0f, 1.0f);
params->addPoint(p);
}
return params;
}
}
fl::vector<XYPathPtr> CreateXYPaths(int width, int height) {
fl::vector<XYPathPtr> out;
out.push_back(XYPath::NewCirclePath(width, height));
out.push_back(XYPath::NewRosePath(width, height));
out.push_back(XYPath::NewHeartPath(width, height));
out.push_back(XYPath::NewArchimedeanSpiralPath(width, height));
out.push_back(XYPath::NewPhyllotaxisPath(width, height));
out.push_back(XYPath::NewGielisCurvePath(width, height));
out.push_back(XYPath::NewCatmullRomPath(width, height, make_path(width, height)));
return out;
}

View File

@@ -0,0 +1,10 @@
#include "fl/xypath.h"
#include "fl/vector.h"
using namespace fl;
// XYPath::NewRosePath(WIDTH, HEIGHT);
fl::vector<XYPathPtr> CreateXYPaths(int width, int height);

View File

@@ -0,0 +1,129 @@
/// @file EaseInOut.ino
/// @brief Demonstrates easing functions with visual curve display
/// @example EaseInOut.ino
///
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
/// 1. Install Fastled: `pip install fastled`
/// 2. cd into this examples page.
/// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open.
#include <FastLED.h>
#include "fl/ease.h"
#include "fl/leds.h"
using namespace fl;
// Matrix configuration
#define MATRIX_WIDTH 100
#define MATRIX_HEIGHT 100
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
#define DATA_PIN 3
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 255
#define MATRIX_SERPENTINE true
// Use LedsXY for splat rendering instead of regular CRGB array
LedsXY<MATRIX_WIDTH, MATRIX_HEIGHT> leds;
// Create XYMap for serpentine 100x100 matrix
XYMap xyMap = XYMap::constructSerpentine(MATRIX_WIDTH, MATRIX_HEIGHT);
UITitle title("EaseInOut");
UIDescription description("Use the xPosition slider to see the ease function curve. Use the Ease Type dropdown to select different easing functions. Use the 16-bit checkbox to toggle between 16-bit (checked) and 8-bit (unchecked) precision.");
// UI Controls
UISlider xPosition("xPosition", 0.0f, 0.0f, 1.0f, 0.01f);
// Create dropdown with descriptive ease function names
fl::string easeOptions[] = {
"None",
"In Quad",
"Out Quad",
"In-Out Quad",
"In Cubic",
"Out Cubic",
"In-Out Cubic",
"In Sine",
"Out Sine",
"In-Out Sine"
};
UIDropdown easeTypeDropdown("Ease Type", easeOptions);
UICheckbox use16Bit("16-bit", true); // Default checked for 16-bit precision
EaseType getEaseType(int value) {
switch (value) {
case 0: return EASE_NONE;
case 1: return EASE_IN_QUAD;
case 2: return EASE_OUT_QUAD;
case 3: return EASE_IN_OUT_QUAD;
case 4: return EASE_IN_CUBIC;
case 5: return EASE_OUT_CUBIC;
case 6: return EASE_IN_OUT_CUBIC;
case 7: return EASE_IN_SINE;
case 8: return EASE_OUT_SINE;
case 9: return EASE_IN_OUT_SINE;
}
FL_ASSERT(false, "Invalid ease type");
return EASE_IN_OUT_QUAD;
}
void setup() {
Serial.begin(115200);
Serial.println("FastLED Ease16InOutQuad Demo - Simple Curve Visualization");
// Add LEDs and set screen map
auto *controller =
&FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
// Convert XYMap to ScreenMap and set it on the controller
fl::ScreenMap screenMap = xyMap.toScreenMap();
screenMap.setDiameter(.5); // Set LED diameter for visualization
controller->setScreenMap(screenMap);
// Configure FastLED
FastLED.setBrightness(BRIGHTNESS);
FastLED.setCorrection(TypicalLEDStrip);
FastLED.setDither(BRIGHTNESS < 255);
// Set default dropdown selection to "In-Out Quad" (index 3)
easeTypeDropdown.setSelectedIndex(3);
}
void loop() {
// Clear the matrix using fl::clear for LedsXY
fl::clear(leds);
// Get the current slider value (0.0 to 1.0)
float sliderValue = xPosition.value();
// Map slider value to X coordinate (0 to width-1)
uint8_t x = map(sliderValue * 1000, 0, 1000, 0, MATRIX_WIDTH - 1);
// Get the selected ease type using the dropdown index
EaseType selectedEaseType = getEaseType(easeTypeDropdown.as_int());
uint8_t y;
if (use16Bit.value()) {
// Use 16-bit precision
uint16_t easeInput = map(sliderValue * 1000, 0, 1000, 0, 65535);
uint16_t easeOutput = ease16(selectedEaseType, easeInput);
y = map(easeOutput, 0, 65535, 0, MATRIX_HEIGHT - 1);
} else {
// Use 8-bit precision
uint8_t easeInput = map(sliderValue * 1000, 0, 1000, 0, 255);
uint8_t easeOutput = ease8(selectedEaseType, easeInput);
y = map(easeOutput, 0, 255, 0, MATRIX_HEIGHT - 1);
}
// Draw white dot at the calculated position using splat rendering
if (x < MATRIX_WIDTH && y < MATRIX_HEIGHT) {
leds(x, y) = CRGB::White;
}
FastLED.show();
}

View File

@@ -0,0 +1,9 @@
#include "fl/sketch_macros.h"
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#include "EaseInOut.h"
#endif

View File

@@ -0,0 +1,205 @@
#ifdef ESP32
/// The Yves ESP32_S3 I2S driver is a driver that uses the I2S peripheral on the ESP32-S3 to drive leds.
/// Originally from: https://github.com/hpwit/I2SClockLessLedDriveresp32s3
///
///
/// This is an advanced driver. It has certain ramifications.
/// - Once flashed, the ESP32-S3 might NOT want to be reprogrammed again. To get around
/// this hold the reset button and release when the flash tool is looking for an
/// an upload port.
/// - Put a delay in the setup function. This is to make it easier to flash the device during developement.
/// - Serial output will mess up the DMA controller. I'm not sure why this is happening
/// but just be aware of it. If your device suddenly stops working, remove the printfs and see if that fixes the problem.
///
/// Is RGBW supported? Yes.
///
/// Is Overclocking supported? Yes. Use this to bend the timeings to support other WS281X variants. Fun fact, just overclock the
/// chipset until the LED starts working.
///
/// What about the new WS2812-5VB leds? Yes, they have 250us timing.
///
/// Why use this?
/// Raw YVes driver needs a perfect parallel rectacngle buffer for operation. In this code we've provided FastLED
/// type bindings.
///
// ArduinoIDE
// Should already be enabled.
//
// PLATFORMIO BUILD FLAGS:
// Define your platformio.ini like so:
//
// PlatformIO
// [env:esp32s3]
// platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip
// framework = arduino
// board = seeed_xiao_esp32s3
#define FASTLED_USES_ESP32S3_I2S // Must define this before including FastLED.h
#include "FastLED.h"
#include "fl/assert.h"
#define NUMSTRIPS 16
#define NUM_LEDS_PER_STRIP 256
#define NUM_LEDS (NUM_LEDS_PER_STRIP * NUMSTRIPS)
// Note that you can use less strips than this.
#define EXAMPLE_PIN_NUM_DATA0 19 // B0
#define EXAMPLE_PIN_NUM_DATA1 45 // B1
#define EXAMPLE_PIN_NUM_DATA2 21 // B2
#define EXAMPLE_PIN_NUM_DATA3 6 // B3
#define EXAMPLE_PIN_NUM_DATA4 7 // B4
#define EXAMPLE_PIN_NUM_DATA5 8 // G0
#define EXAMPLE_PIN_NUM_DATA6 9 // G1
#define EXAMPLE_PIN_NUM_DATA7 10 // G2
#define EXAMPLE_PIN_NUM_DATA8 11 // G3
#define EXAMPLE_PIN_NUM_DATA9 12 // G4
#define EXAMPLE_PIN_NUM_DATA10 13 // G5
#define EXAMPLE_PIN_NUM_DATA11 14 // R0
#define EXAMPLE_PIN_NUM_DATA12 15 // R1
#define EXAMPLE_PIN_NUM_DATA13 16 // R2
#define EXAMPLE_PIN_NUM_DATA14 17 // R3
#define EXAMPLE_PIN_NUM_DATA15 18 // R4
// Users say you can use a lot less strips. Experiment around and find out!
// Please comment at reddit.com/r/fastled and let us know if you have problems.
// Or send us a picture of your Triumps!
int PINS[] = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
EXAMPLE_PIN_NUM_DATA3,
EXAMPLE_PIN_NUM_DATA4,
EXAMPLE_PIN_NUM_DATA5,
EXAMPLE_PIN_NUM_DATA6,
EXAMPLE_PIN_NUM_DATA7,
EXAMPLE_PIN_NUM_DATA8,
EXAMPLE_PIN_NUM_DATA9,
EXAMPLE_PIN_NUM_DATA10,
EXAMPLE_PIN_NUM_DATA11,
EXAMPLE_PIN_NUM_DATA12,
EXAMPLE_PIN_NUM_DATA13,
EXAMPLE_PIN_NUM_DATA14,
EXAMPLE_PIN_NUM_DATA15
};
CRGB leds[NUM_LEDS];
void setup_i2s() {
// Note, in this case we are using contingious memory for the leds. But this is not required.
// Each strip can be a different size and the FastLED api will upscale the smaller strips to the largest strip.
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA0, GRB>(
leds + (0 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA1, GRB>(
leds + (1 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA2, GRB>(
leds + (2 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA3, GRB>(
leds + (3 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA4, GRB>(
leds + (4 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA5, GRB>(
leds + (5 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA6, GRB>(
leds + (6 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA7, GRB>(
leds + (7 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA8, GRB>(
leds + (8 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA9, GRB>(
leds + (9 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA10, GRB>(
leds + (10 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA11, GRB>(
leds + (11 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA12, GRB>(
leds + (12 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA13, GRB>(
leds + (13 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA14, GRB>(
leds + (14 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA15, GRB>(
leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(57600);
// This is used so that you can see if PSRAM is enabled. If not, we will crash in setup() or in loop().
log_d("Total heap: %d", ESP.getHeapSize());
log_d("Free heap: %d", ESP.getFreeHeap());
log_d("Total PSRAM: %d", ESP.getPsramSize()); // If this prints out 0, then PSRAM is not enabled.
log_d("Free PSRAM: %d", ESP.getFreePsram());
log_d("waiting 6 seconds before startup");
delay(6000); // The long reset time here is to make it easier to flash the device during the development process.
setup_i2s();
FastLED.setBrightness(32);
}
void fill_rainbow(CRGB* all_leds) {
static int s_offset = 0;
for (int j = 0; j < NUMSTRIPS; j++) {
for (int i = 0; i < NUM_LEDS_PER_STRIP; i++) {
int idx = (i + s_offset) % NUM_LEDS_PER_STRIP + NUM_LEDS_PER_STRIP * j;
all_leds[idx] = CHSV(i, 255, 255);
}
}
s_offset++;
}
void loop() {
fill_rainbow(leds);
FastLED.show();
}
#else // ESP32
// Non-ESP32 platform - provide minimal example for compilation testing
#include "FastLED.h"
#define NUM_LEDS 16
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}
void loop() {
fill_rainbow(leds, NUM_LEDS, 0, 7);
FastLED.show();
delay(50);
}
#endif // ESP32

View File

@@ -0,0 +1,19 @@
#ifndef ESP32
#define IS_ESP32_S3 0
#else
#include "sdkconfig.h"
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define IS_ESP32_S3 1
#else
#define IS_ESP32_S3 0
#endif // CONFIG_IDF_TARGET_ESP32
#endif // ESP32
#if IS_ESP32_S3
#include "Esp32S3I2SDemo.h"
#else
#include "platforms/sketch_fake.hpp"
#endif

View File

@@ -0,0 +1,74 @@
// Simple test for the I2S on the ESP32dev board.
// IMPORTANT:
// This is using examples is built on esp-idf 4.x. This existed prior to Arduino Core 3.0.0.
// To use this example, you MUST downgrade to Arduino Core < 3.0.0
// or it won't work on Arduino.
#ifdef ESP32
#define FASTLED_ESP32_I2S
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS 1
// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 3
// Define the array of leds
CRGB leds[NUM_LEDS];
void setup() {
// Uncomment/edit one of the following lines for your leds arrangement.
// ## Clockless types ##
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // GRB ordering is assumed
}
void loop() {
// Turn the LED on, then pause
leds[0] = CRGB::Red;
FastLED.show();
delay(500);
// Now turn the LED off, then pause
leds[0] = CRGB::Black;
FastLED.show();
delay(500);
// This is a no-op but tests that we have access to gCntBuffer, part of the
// i2s api. You can delete this in your own sketch. It's only here for testing
// purposes.
if (false) {
int value = gCntBuffer;
value++;
}
}
#else // ESP32
// Non-ESP32 platform - provide minimal example for compilation testing
#include <FastLED.h>
#define NUM_LEDS 1
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}
void loop() {
leds[0] = CRGB::Red;
FastLED.show();
delay(500);
leds[0] = CRGB::Black;
FastLED.show();
delay(500);
}
#endif // ESP32

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