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

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

View File

@@ -0,0 +1,25 @@
#include "fl/has_include.h"
// Platform must be esp32.
#if !defined(ESP32) || !FL_HAS_INCLUDE("sdkconfig.h")
#define IS_ESP32_DEV 0
#else
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#define IS_ESP32_DEV 1
#else
#define IS_ESP32_DEV 0
#endif
#endif
#if IS_ESP32_DEV
#include "EspI2SDemo.h"
#else
#include "platforms/sketch_fake.hpp"
#endif

View File

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

View File

@@ -0,0 +1,811 @@
/*
Festival Stick - Corkscrew LED Mapping Demo
This example demonstrates proper corkscrew LED mapping for a festival stick
(19+ turns, 288 LEDs) using the new Corkscrew ScreenMap functionality.
Key Features:
- Uses Corkscrew.toScreenMap() for accurate web interface visualization
- Draws patterns into a rectangular grid (frameBuffer)
- Maps the rectangular grid to the corkscrew LED positions using readFrom()
- Supports both noise patterns and manual LED positioning
- Proper color boost and brightness controls
Workflow:
1. Draw patterns into frameBuffer (rectangular grid for easy 2D drawing)
2. Use corkscrew.readFrom(frameBuffer) to map grid to corkscrew LED positions
3. Display the corkscrew buffer directly via FastLED
4. Web interface shows actual corkscrew spiral shape via ScreenMap
*/
#include "FastLED.h"
#include "fl/compiler_control.h"
#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 "fl/array.h"
#include "fx/2d/wave.h"
#include "fx/2d/blend.h"
#include "fx/fx_engine.h"
#include "fx/2d/animartrix.hpp"
// #include "vec3.h"
using namespace fl;
#ifndef PIN_DATA
#define PIN_DATA 1 // Universally available pin
#endif
#ifndef PIN_CLOCK
#define PIN_CLOCK 2 // Universally available pin
#endif
#ifdef TEST
#define NUM_LEDS 4
#define CORKSCREW_TURNS 2 // Default to 19 turns
#else
#define NUM_LEDS 288
#define CORKSCREW_TURNS 19.25 // Default to 19 turns
#endif
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
UITitle festivalStickTitle("Festival Stick - Advanced Version");
UIDescription festivalStickDescription(
"# Festival Stick Demo\n\n"
"This example demonstrates **proper corkscrew LED mapping** for a festival stick using FastLED's advanced mapping capabilities.\n\n"
"## Key Features\n"
"- **19+ turns** with 288 LEDs total\n"
"- Uses `Corkscrew.toScreenMap()` for accurate web interface visualization\n"
"- Multiple render modes: **Noise**, **Position**, **Fire**, **Wave**, and **Animartrix** effects\n"
"- Real-time cylindrical surface mapping\n"
"- **Wave mode**: Cylindrical 2D wave simulation with ripple effects and configurable blur\n"
"- **Animartrix mode**: Advanced 2D animation effects with polar coordinate patterns\n\n"
"## How It Works\n"
"1. Draws patterns into a rectangular grid (`frameBuffer`)\n"
"2. Maps the grid to corkscrew LED positions using `readFrom()`\n"
"3. Web interface shows the actual spiral shape via ScreenMap\n\n"
"*Select different render modes and adjust parameters to see various effects!*");
// UIHelp festivalStickHelp("Festival Stick - Advanced Guide");
// UIHelp corkscrewMappingHelp("Understanding Corkscrew Mapping");
// UIHelp uiControlsHelp("UI Controls Guide");
UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
UISlider positionCoarse("Position Coarse (10x)", 0.0f, 0.0f, 1.0f, 0.01f);
UISlider positionFine("Position Fine (1x)", 0.0f, 0.0f, 0.1f, 0.001f);
UISlider positionExtraFine("Position Extra Fine (0.1x)", 0.0f, 0.0f, 0.01f, 0.0001f);
UISlider brightness("Brightness", 255, 0, 255, 1);
UICheckbox autoAdvance("Auto Advance", true);
UICheckbox allWhite("All White", false);
UICheckbox splatRendering("Splat Rendering", true);
// Noise controls (grouped under noiseGroup)
UISlider noiseScale("Noise Scale", 100, 10, 200, 5);
UISlider noiseSpeed("Noise Speed", 4, 1, 100, 1);
// UIDropdown examples - noise-related color palette
string paletteOptions[] = {"Party", "Heat", "Ocean", "Forest", "Rainbow"};
string renderModeOptions[] = { "Wave", "Animartrix", "Noise", "Position", "Fire" };
UIDropdown paletteDropdown("Color Palette", paletteOptions);
UIDropdown renderModeDropdown("Render Mode", renderModeOptions);
// fl::array<fl::pair<int, fl::string>> easeInfo = {
// pair(EASE_IN_QUAD, "EASE_IN_QUAD"),
// pair(EASE_OUT_QUAD, "EASE_OUT_QUAD"),
// pair(EASE_IN_OUT_QUAD, "EASE_IN_OUT_QUAD"),
// pair(EASE_IN_CUBIC, "EASE_IN_CUBIC"),
// pair(EASE_OUT_CUBIC, "EASE_OUT_CUBIC"),
// pair(EASE_IN_OUT_CUBIC, "EASE_IN_OUT_CUBIC"),
// pair(EASE_IN_SINE, "EASE_IN_SINE"),
// pair(EASE_OUT_SINE, "EASE_OUT_SINE"),
// pair(EASE_IN_OUT_SINE, "EASE_IN_OUT_SINE")
// };
fl::vector<fl::string> easeInfo = {
"EASE_NONE",
"EASE_IN_QUAD",
"EASE_OUT_QUAD",
"EASE_IN_OUT_QUAD",
"EASE_IN_CUBIC",
"EASE_OUT_CUBIC",
"EASE_IN_OUT_CUBIC",
"EASE_IN_SINE",
"EASE_OUT_SINE",
"EASE_IN_OUT_SINE"
};
EaseType getEaseType(fl::string value) {
if (value == "EASE_NONE") {
return EASE_NONE;
} else if (value == "EASE_IN_QUAD") {
return EASE_IN_QUAD;
} else if (value == "EASE_OUT_QUAD") {
return EASE_OUT_QUAD;
} else if (value == "EASE_IN_OUT_QUAD") {
return EASE_IN_OUT_QUAD;
} else if (value == "EASE_IN_CUBIC") {
return EASE_IN_CUBIC;
} else if (value == "EASE_OUT_CUBIC") {
return EASE_OUT_CUBIC;
} else if (value == "EASE_IN_OUT_CUBIC") {
return EASE_IN_OUT_CUBIC;
} else if (value == "EASE_IN_SINE") {
return EASE_IN_SINE;
} else if (value == "EASE_OUT_SINE") {
return EASE_OUT_SINE;
} else if (value == "EASE_IN_OUT_SINE") {
return EASE_IN_OUT_SINE;
} else {
return EASE_NONE;
}
}
// Color boost controls
UIDropdown saturationFunction("Saturation Function", easeInfo);
UIDropdown luminanceFunction("Luminance Function", easeInfo);
// Fire-related UI controls (added for cylindrical fire effect)
UISlider fireScaleXY("Fire Scale", 8, 1, 100, 1);
UISlider fireSpeedY("Fire SpeedY", 1.3, 1, 6, .1);
UISlider fireScaleX("Fire ScaleX", .3, 0.1, 3, .01);
UISlider fireInvSpeedZ("Fire Inverse SpeedZ", 20, 1, 100, 1);
UINumberField firePalette("Fire Palette", 0, 0, 2);
// Wave-related UI controls (cylindrical wave effects)
UISlider waveSpeed("Wave Speed", 0.03f, 0.0f, 1.0f, 0.01f);
UISlider waveDampening("Wave Dampening", 9.1f, 0.0f, 20.0f, 0.1f);
UICheckbox waveHalfDuplex("Wave Half Duplex", true);
UICheckbox waveAutoTrigger("Wave Auto Trigger", true);
UISlider waveTriggerSpeed("Wave Trigger Speed", 0.5f, 0.0f, 1.0f, 0.01f);
UIButton waveTriggerButton("Trigger Wave");
UINumberField wavePalette("Wave Palette", 0, 0, 2);
// Wave blur controls (added for smoother wave effects)
UISlider waveBlurAmount("Wave Blur Amount", 50, 0, 172, 1);
UISlider waveBlurPasses("Wave Blur Passes", 1, 1, 10, 1);
// Fire color palettes (from FireCylinder)
DEFINE_GRADIENT_PALETTE(firepal){
0, 0, 0, 0,
32, 255, 0, 0,
190, 255, 255, 0,
255, 255, 255, 255
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
0, 0, 0, 0,
32, 0, 70, 0,
190, 57, 255, 20,
255, 255, 255, 255
};
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
0, 0, 0, 0,
32, 0, 0, 70,
128, 20, 57, 255,
255, 255, 255, 255
};
// Wave color palettes (for cylindrical wave effects)
DEFINE_GRADIENT_PALETTE(waveBluepal){
0, 0, 0, 0, // Black (no wave)
32, 0, 0, 70, // Dark blue (low wave)
128, 20, 57, 255, // Electric blue (medium wave)
255, 255, 255, 255 // White (high wave)
};
DEFINE_GRADIENT_PALETTE(waveGreenpal){
0, 0, 0, 0, // Black (no wave)
8, 128, 64, 64, // Green with red tint (very low wave)
16, 255, 222, 222, // Pinkish red (low wave)
64, 255, 255, 255, // White (medium wave)
255, 255, 255, 255 // White (high wave)
};
DEFINE_GRADIENT_PALETTE(waveRainbowpal){
0, 255, 0, 0, // Red (no wave)
64, 255, 127, 0, // Orange (low wave)
128, 255, 255, 0, // Yellow (medium wave)
192, 0, 255, 0, // Green (high wave)
255, 0, 0, 255 // Blue (maximum wave)
};
// Create UIGroup for noise controls using variadic constructor
// This automatically assigns all specified controls to the "Noise Controls" group
UIGroup noiseGroup("Noise Controls", noiseScale, noiseSpeed, paletteDropdown);
UIGroup fireGroup("Fire Controls", fireScaleXY, fireSpeedY, fireScaleX, fireInvSpeedZ, firePalette);
UIGroup waveGroup("Wave Controls", waveSpeed, waveDampening, waveHalfDuplex, waveAutoTrigger, waveTriggerSpeed, waveTriggerButton, wavePalette, waveBlurAmount, waveBlurPasses);
UIGroup renderGroup("Render Options", renderModeDropdown, splatRendering, allWhite, brightness);
UIGroup colorBoostGroup("Color Boost", saturationFunction, luminanceFunction);
UIGroup pointGraphicsGroup("Point Graphics Mode", speed, positionCoarse, positionFine, positionExtraFine, autoAdvance);
// Animartrix-related UI controls
UINumberField animartrixIndex("Animartrix Animation", 5, 0, NUM_ANIMATIONS - 1);
UINumberField animartrixColorOrder("Animartrix Color Order", 0, 0, 5);
UISlider animartrixTimeSpeed("Animartrix Time Speed", 1, -10, 10, .1);
UIGroup animartrixGroup("Animartrix Controls", animartrixIndex, animartrixTimeSpeed, animartrixColorOrder);
// Color palette for noise
CRGBPalette16 noisePalette = PartyColors_p;
uint8_t colorLoop = 1;
// Option 1: Runtime Corkscrew (flexible, configurable at runtime)
Corkscrew corkscrew(CORKSCREW_TURNS, NUM_LEDS);
// Simple position tracking - one variable for both modes
static float currentPosition = 0.0f;
static uint32_t lastUpdateTime = 0;
// Wave effect globals
static uint32_t nextWaveTrigger = 0;
// Option 2: Constexpr dimensions for compile-time array sizing
constexpr uint16_t CORKSCREW_WIDTH =
calculateCorkscrewWidth(CORKSCREW_TURNS, NUM_LEDS);
constexpr uint16_t CORKSCREW_HEIGHT =
calculateCorkscrewHeight(CORKSCREW_TURNS, NUM_LEDS);
// Now you can use these for array initialization:
// CRGB frameBuffer[CORKSCREW_WIDTH * CORKSCREW_HEIGHT]; // Compile-time sized
// array
// Create a corkscrew with:
// - 30cm total length (300mm)
// - 5cm width (50mm)
// - 2mm LED inner diameter
// - 24 LEDs per turn
// ScreenMap screenMap = makeCorkScrew(NUM_LEDS,
// 300.0f, 50.0f, 2.0f, 24.0f);
// vector<vec3f> mapCorkScrew = makeCorkScrew(args);
ScreenMap screenMap;
fl::shared_ptr<Grid<CRGB>> frameBufferPtr;
// Wave effect objects - declared here but initialized in setup()
WaveFxPtr waveFx;
Blend2dPtr waveBlend;
// Animartrix effect objects - declared here but initialized in setup()
fl::unique_ptr<Animartrix> animartrix;
fl::unique_ptr<FxEngine> fxEngine;
WaveCrgbGradientMapPtr crgMap = fl::make_shared<WaveCrgbGradientMap>();
void setup() {
// Use constexpr dimensions (computed at compile time)
constexpr int width = CORKSCREW_WIDTH; // = 16
constexpr int height = CORKSCREW_HEIGHT; // = 18
// Noise controls are now automatically grouped by the UIGroup constructor
// The noiseGroup variadic constructor automatically called setGroup() on all controls
// Or use runtime corkscrew for dynamic sizing
// int width = corkscrew.cylinder_width();
// int height = corkscrew.cylinder_height();
XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0);
// Use the corkscrew's internal buffer for the LED strip
CLEDController *controller =
&FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(corkscrew.rawData(), NUM_LEDS);
// CLEDController *controller =
// &FastLED.addLeds<WS2812, 3, BGR>(stripLeds, NUM_LEDS);
// NEW: Create ScreenMap directly from Corkscrew using toScreenMap()
// This maps each LED index to its exact position on the corkscrew spiral
// instead of using a rectangular grid mapping
ScreenMap corkscrewScreenMap = corkscrew.toScreenMap(0.2f);
// OLD WAY (rectangular grid - not accurate for corkscrew visualization):
// 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 spiral shape
controller->setScreenMap(corkscrewScreenMap);
// Initialize wave effects for cylindrical surface
XYMap xyRect(width, height, false); // Rectangular grid for wave simulation
WaveFx::Args waveArgs;
waveArgs.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
waveArgs.half_duplex = true; // Only positive waves
waveArgs.auto_updates = true; // Auto-update simulation
waveArgs.speed = 0.16f; // Wave propagation speed
waveArgs.dampening = 6.0f; // Wave energy loss
waveArgs.x_cyclical = true; // Enable cylindrical wrapping!
waveArgs.crgbMap = fl::make_shared<WaveCrgbGradientMap>(waveBluepal); // Default color palette
// Create wave effect with cylindrical mapping
waveFx = fl::make_shared<WaveFx>(xyRect, waveArgs);
// Create blender for wave effects (allows multiple wave layers in future)
waveBlend = fl::make_shared<Blend2d>(xyRect);
waveBlend->add(waveFx);
// Initialize Animartrix effect
XYMap animartrixXyMap = XYMap::constructRectangularGrid(width, height, 0);
animartrix.reset(new Animartrix(animartrixXyMap, POLAR_WAVES));
fxEngine.reset(new FxEngine(width * height));
fxEngine->addFx(*animartrix);
// Demonstrate UIGroup functionality for noise controls
FL_WARN("Noise UI Group initialized: " << noiseGroup.name());
FL_WARN(" This group contains noise pattern controls:");
FL_WARN(" - Use Noise Pattern toggle");
FL_WARN(" - Noise Scale and Speed sliders");
FL_WARN(" - Color Palette selection for noise");
FL_WARN(" UIGroup automatically applied group membership via variadic constructor");
// Set initial dropdown selections
paletteDropdown.setSelectedIndex(0); // Party
renderModeDropdown.setSelectedIndex(0); // Fire (new default)
// Add onChange callbacks for dropdowns
paletteDropdown.onChanged([](UIDropdown &dropdown) {
string selectedPalette = dropdown.value();
FL_WARN("Noise palette changed to: " << selectedPalette);
if (selectedPalette == "Party") {
noisePalette = PartyColors_p;
} else if (selectedPalette == "Heat") {
noisePalette = HeatColors_p;
} else if (selectedPalette == "Ocean") {
noisePalette = OceanColors_p;
} else if (selectedPalette == "Forest") {
noisePalette = ForestColors_p;
} else if (selectedPalette == "Rainbow") {
noisePalette = RainbowColors_p;
}
});
renderModeDropdown.onChanged([](UIDropdown &dropdown) {
string mode = dropdown.value();
// Simple example of using getOption()
for(size_t i = 0; i < dropdown.getOptionCount(); i++) {
if(dropdown.getOption(i) == mode) {
FL_WARN("Render mode changed to: " << mode);
}
}
});
// Add onChange callback for animartrix color order
animartrixColorOrder.onChanged([](int value) {
EOrder order = RGB;
switch(value) {
case 0: order = RGB; break;
case 1: order = RBG; break;
case 2: order = GRB; break;
case 3: order = GBR; break;
case 4: order = BRG; break;
case 5: order = BGR; break;
}
if (animartrix.get()) {
animartrix->setColorOrder(order);
}
});
waveFx->setCrgbMap(crgMap);
frameBufferPtr = corkscrew.getOrCreateInputSurface();
}
FL_OPTIMIZATION_LEVEL_O0_BEGIN // Works around a compile bug in clang 19
float get_position(uint32_t now) {
if (autoAdvance.value()) {
// Check if auto-advance was just enabled
// Auto-advance mode: increment smoothly from current position
float elapsedSeconds = float(now - lastUpdateTime) / 1000.0f;
float increment = elapsedSeconds * speed.value() *
0.3f; // Make it 1/20th the original speed
currentPosition = fmodf(currentPosition + increment, 1.0f);
lastUpdateTime = now;
return currentPosition;
} else {
// Manual mode: use the dual slider control
float combinedPosition = positionCoarse.value() + positionFine.value() + positionExtraFine.value();
// Clamp to ensure we don't exceed 1.0
if (combinedPosition > 1.0f)
combinedPosition = 1.0f;
return combinedPosition;
}
}
FL_OPTIMIZATION_LEVEL_O0_END
void fillFrameBufferNoise() {
// Get current UI values
uint8_t noise_scale = noiseScale.value();
uint8_t noise_speed = noiseSpeed.value();
// Derive noise coordinates from current time instead of forward iteration
uint32_t now = millis();
uint16_t noise_z = now * noise_speed / 10; // Primary time dimension
uint16_t noise_x = now * noise_speed / 80; // Slow drift in x
uint16_t noise_y = now * noise_speed / 160; // Even slower drift in y (opposite direction)
int width = frameBufferPtr->width();
int height = frameBufferPtr->height();
// Data smoothing for low speeds (from NoisePlusPalette example)
uint8_t dataSmoothing = 0;
if(noise_speed < 50) {
dataSmoothing = 200 - (noise_speed * 4);
}
// Generate noise for each pixel in the frame buffer using cylindrical mapping
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
// Convert rectangular coordinates to cylindrical coordinates
// Map x to angle (0 to 2*PI), y remains as height
float angle = (float(x) / float(width)) * 2.0f * PI;
// Convert cylindrical coordinates to cartesian for noise sampling
// Use the noise_scale to control the cylinder size in noise space
float cylinder_radius = noise_scale; // Use the existing noise_scale parameter
// Calculate cartesian coordinates on the cylinder surface
float noise_x_cyl = cos(angle) * cylinder_radius;
float noise_y_cyl = sin(angle) * cylinder_radius;
float noise_z_height = float(y) * noise_scale; // Height component
// Apply time-based offsets
int xoffset = int(noise_x_cyl) + noise_x;
int yoffset = int(noise_y_cyl) + noise_y;
int zoffset = int(noise_z_height) + noise_z;
// Generate 8-bit noise value using 3D Perlin noise with cylindrical coordinates
uint8_t data = inoise8(xoffset, yoffset, zoffset);
// Expand the range from ~16-238 to 0-255 (from NoisePlusPalette)
data = qsub8(data, 16);
data = qadd8(data, scale8(data, 39));
// Apply data smoothing if enabled
if(dataSmoothing) {
CRGB oldColor = frameBufferPtr->at(x, y);
uint8_t olddata = (oldColor.r + oldColor.g + oldColor.b) / 3; // Simple brightness extraction
uint8_t newdata = scale8(olddata, dataSmoothing) + scale8(data, 256 - dataSmoothing);
data = newdata;
}
// Map noise to color using palette (adapted from NoisePlusPalette)
uint8_t index = data;
uint8_t bri = data;
// Add color cycling if enabled - also derive from time
uint8_t ihue = 0;
if(colorLoop) {
ihue = (now / 100) % 256; // Derive hue from time instead of incrementing
index += ihue;
}
// Enhance brightness (from NoisePlusPalette example)
// if(bri > 127) {
// //bri = 255;
// } else {
// //bri = dim8_raw(bri * 2);
// }
// Get color from palette and set pixel
CRGB color = ColorFromPalette(noisePalette, index, bri);
// Apply color boost using ease functions
EaseType sat_ease = getEaseType(saturationFunction.value());
EaseType lum_ease = getEaseType(luminanceFunction.value());
color = color.colorBoost(sat_ease, lum_ease);
frameBufferPtr->at(x, y) = color;
}
}
}
void drawNoise(uint32_t now) {
FL_UNUSED(now);
fillFrameBufferNoise();
}
void draw(float pos) {
if (splatRendering) {
Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos);
//FL_WARN("pos_tile: " << pos_tile);
CRGB color = CRGB::Blue;
// Apply color boost using ease functions
EaseType sat_ease = getEaseType(saturationFunction.value());
EaseType lum_ease = getEaseType(luminanceFunction.value());
color = color.colorBoost(sat_ease, lum_ease);
// 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) {
Tile2x2_u8_wrap::Entry 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
frameBufferPtr->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>(pos_vec2f.x, pos_vec2f.y);
CRGB color = CRGB::Blue;
// Apply color boost using ease functions
EaseType sat_ease = getEaseType(saturationFunction.value());
EaseType lum_ease = getEaseType(luminanceFunction.value());
color = color.colorBoost(sat_ease, lum_ease);
// Now map the cork screw position to the cylindrical buffer that we
// will draw.
frameBufferPtr->at(pos_i16.x, pos_i16.y) = color; // Draw a blue pixel at (w, h)
}
}
CRGBPalette16 getFirePalette() {
int paletteIndex = (int)firePalette.value();
switch (paletteIndex) {
case 0:
return firepal;
case 1:
return electricGreenFirePal;
case 2:
return electricBlueFirePal;
default:
return firepal;
}
}
uint8_t getFirePaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height,
uint32_t y_speed) {
uint16_t scale = fireScaleXY.as<uint16_t>();
float xf = (float)width / (float)max_width;
uint8_t x = (uint8_t)(xf * 255);
uint32_t cosx = cos8(x);
uint32_t sinx = sin8(x);
float trig_scale = scale * fireScaleX.value();
cosx *= trig_scale;
sinx *= trig_scale;
uint32_t y = height * scale + y_speed;
uint16_t z = millis32 / fireInvSpeedZ.as<uint16_t>();
uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, z << 8);
uint8_t noise_val = noise16 >> 8;
int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 /
(max_height - 1);
return qsub8(noise_val, subtraction_factor);
}
void fillFrameBufferFire(uint32_t now) {
CRGBPalette16 myPal = getFirePalette();
// Calculate the current y-offset for animation (makes the fire move)
uint32_t y_speed = now * fireSpeedY.value();
int width = frameBufferPtr->width();
int height = frameBufferPtr->height();
// Loop through every pixel in our cylindrical matrix
for (int w = 0; w < width; w++) {
for (int h = 0; h < height; h++) {
// Calculate which color to use from our palette for this pixel
uint8_t palette_index =
getFirePaletteIndex(now, w, width, h, height, y_speed);
// Get the actual RGB color from the palette
CRGB color = ColorFromPalette(myPal, palette_index, 255);
// Apply color boost using ease functions
EaseType sat_ease = getEaseType(saturationFunction.value());
EaseType lum_ease = getEaseType(luminanceFunction.value());
color = color.colorBoost(sat_ease, lum_ease);
// Set the pixel in the frame buffer
// Flip coordinates to make fire rise from bottom
frameBufferPtr->at((width - 1) - w, (height - 1) - h) = color;
}
}
}
void drawFire(uint32_t now) {
fillFrameBufferFire(now);
}
// Wave effect helper functions
CRGBPalette16 getWavePalette() {
int paletteIndex = (int)wavePalette.value();
switch (paletteIndex) {
case 0:
return waveBluepal; // Electric blue waves
case 1:
return waveGreenpal; // Green/red waves
case 2:
return waveRainbowpal; // Rainbow waves
default:
return waveBluepal; // Default to blue
}
}
void triggerWaveRipple() {
// Create a ripple at a random position within the central area
float perc = 0.15f; // 15% margin from edges
int width = corkscrew.cylinderWidth();
int height = corkscrew.cylinderHeight();
int min_x = perc * width;
int max_x = (1 - perc) * width;
int min_y = perc * height;
int max_y = (1 - perc) * height;
int x = random8(min_x, max_x);
int y = random8(min_y, max_y);
// Trigger a 2x2 wave ripple for more punch (compensates for blur reduction)
float ripple_strength = 1.5f; // Higher value for more impact
waveFx->setf(x, y, ripple_strength);
waveFx->setf(x + 1, y, ripple_strength);
waveFx->setf(x, y + 1, ripple_strength);
waveFx->setf(x + 1, y + 1, ripple_strength);
FL_WARN("Wave ripple triggered at (" << x << ", " << y << ") with 2x2 pattern");
}
void processWaveAutoTrigger(uint32_t now) {
// Handle automatic wave triggering
if (waveAutoTrigger.value()) {
if (now >= nextWaveTrigger) {
triggerWaveRipple();
// Calculate next trigger time based on speed
float speed = 1.0f - waveTriggerSpeed.value();
uint32_t min_interval = (uint32_t)(500 * speed); // Minimum 500ms * speed
uint32_t max_interval = (uint32_t)(3000 * speed); // Maximum 3000ms * speed
// Ensure valid range
uint32_t min = MIN(min_interval, max_interval);
uint32_t max = MAX(min_interval, max_interval);
if (min >= max) max = min + 1;
nextWaveTrigger = now + random16(min, max);
}
}
}
void drawWave(uint32_t now) {
// Update wave parameters from UI
waveFx->setSpeed(waveSpeed.value());
waveFx->setDampening(waveDampening.value());
waveFx->setHalfDuplex(waveHalfDuplex.value());
waveFx->setXCylindrical(true); // Always keep cylindrical for corkscrew
// Update wave color palette
CRGBPalette16 currentPalette = getWavePalette();
crgMap->setGradient(currentPalette);
// Apply blur settings to the wave blend (for smoother wave effects)
waveBlend->setGlobalBlurAmount(waveBlurAmount.value());
waveBlend->setGlobalBlurPasses(waveBlurPasses.value());
// Check if manual trigger button was pressed
if (waveTriggerButton.value()) {
triggerWaveRipple();
}
// Handle auto-triggering
processWaveAutoTrigger(now);
// Draw the wave effect directly to the frame buffer
// Create a DrawContext for the wave renderer
Fx::DrawContext waveContext(now, frameBufferPtr->data());
waveBlend->draw(waveContext);
}
void drawAnimartrix(uint32_t now) {
// Update animartrix parameters from UI
fxEngine->setSpeed(animartrixTimeSpeed.value());
// Handle animation index changes
static int lastAnimartrixIndex = -1;
if (animartrixIndex.value() != lastAnimartrixIndex) {
lastAnimartrixIndex = animartrixIndex.value();
animartrix->fxSet(animartrixIndex.value());
}
// Draw the animartrix effect directly to the frame buffer
CRGB* dst = corkscrew.rawData();
fxEngine->draw(now, dst);
}
void loop() {
delay(4);
uint32_t now = millis();
frameBufferPtr->clear();
if (allWhite) {
CRGB whiteColor = CRGB(8, 8, 8);
for (u32 x = 0; x < frameBufferPtr->width(); x++) {
for (u32 y = 0; y < frameBufferPtr->height(); y++) {
frameBufferPtr->at(x, y) = whiteColor;
}
}
}
// Update the corkscrew mapping with auto-advance or manual position control
float combinedPosition = get_position(now);
float pos = combinedPosition * (corkscrew.size() - 1);
if (renderModeDropdown.value() == "Noise") {
drawNoise(now);
} else if (renderModeDropdown.value() == "Fire") {
drawFire(now);
} else if (renderModeDropdown.value() == "Wave") {
drawWave(now);
} else if (renderModeDropdown.value() == "Animartrix") {
drawAnimartrix(now);
} else {
draw(pos);
}
// Use the new readFrom workflow:
// 1. Read directly from the frameBuffer Grid into the corkscrew's internal buffer
// use_multi_sampling = true will use multi-sampling to sample from the source grid,
// this will give a little bit better accuracy and the screenmap will be more accurate.
const bool use_multi_sampling = splatRendering;
// corkscrew.readFrom(frameBuffer, use_multi_sampling);
corkscrew.draw(use_multi_sampling);
// The corkscrew's buffer is now populated and FastLED will display it directly
FastLED.setBrightness(brightness.value());
FastLED.show();
}

View File

@@ -0,0 +1,220 @@
/*
Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of
a wooden walking stick commonly found on amazon.A0
The UI screenmap projects this cork screw into polar coordinates, so that the LEDs are
mapped to a sprial, with the inner portion of the spiral being the top, the outer
most portion being the bottom.
*/
#include "fl/assert.h"
#include "fl/screenmap.h"
#include "fl/warn.h"
#include "noise.h"
#include <FastLED.h>
// #include "vec3.h"
using namespace fl;
// Power management settings
#define VOLTS 5
#define MAX_AMPS 1
#define PIN_DATA 9
#define PIN_CLOCK 7
// Pin could have been tied to ground, instead it's tied to another pin.
#define PIN_BUTTON 1
#define PIN_GRND 2
#define NUM_LEDS 288
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
UITitle festivalStickTitle("Festival Stick - Classic Version");
UIDescription festivalStickDescription(
"Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. Super simple but very awesome looking. "
"This classic version uses 3D Perlin noise to create organic, flowing patterns around the cylindrical surface. "
"Assumes dense 144 LEDs/meter (288 total LEDs).");
// UIHelp festivalStickHelp("Festival Stick - Classic Guide");
// UIHelp technicalHelp("Technical Details - Classic Festival Stick");
// UIHelp usageHelp("Usage Guide - Classic Festival Stick");
// UIHelp physicalBuildHelp("Building Your Festival Stick");
UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f);
UIButton button("Button");
// Adding a brightness slider
UISlider brightness("Brightness", 16, 0, 255, 1); // Brightness from 0 to 255
CRGB leds[NUM_LEDS];
// fl::vector<vec3f>
struct corkscrew_args {
int num_leds = NUM_LEDS;
float leds_per_turn = 15.5;
float width_cm = 1.0;
};
fl::vector<vec3f> makeCorkScrew(corkscrew_args args = corkscrew_args()) {
// int num_leds, float leds_per_turn, float width_cm
int num_leds = args.num_leds;
float leds_per_turn = args.leds_per_turn;
float width_cm = args.width_cm;
const float circumference = leds_per_turn;
const float radius = circumference / (2.0 * PI); // radius in mm
const float angle_per_led = 2.0 * PI / leds_per_turn; // degrees per LED
const float total_angle_radians = angle_per_led * num_leds;
const float total_turns = total_angle_radians / (2.0 * PI); // total turns
const float height_per_turn_cm = width_cm; // 10cm height per turn
const float height_per_led =
height_per_turn_cm /
leds_per_turn; // this is the changing height per led.
const float total_height =
height_per_turn_cm * total_turns; // total height of the corkscrew
fl::vector<vec3f> out;
for (int i = 0; i < num_leds; i++) {
float angle = i * angle_per_led; // angle in radians
float height = (i / leds_per_turn) * height_per_turn_cm; // height in cm
// Calculate the x, y, z coordinates for the corkscrew
float x = radius * cos(angle); // x coordinate
float z = radius * sin(angle); // y coordinate
float y = height; // z coordinate
// Store the 3D coordinates in the vector
vec3f led_position(x, y, z);
// screenMap.set(i, led_position);
out.push_back(led_position);
}
return out;
}
fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) {
// Create a ScreenMap for the corkscrew
fl::vector<vec2f> points(args.num_leds);
int num_leds = args.num_leds;
float leds_per_turn = args.leds_per_turn;
float width_cm = args.width_cm;
const float circumference = leds_per_turn;
const float radius = circumference / (2.0 * PI); // radius in mm
const float angle_per_led = 2.0 * PI / leds_per_turn; // degrees per LED
const float height_per_turn_cm = width_cm; // 10cm height per turn
const float height_per_led =
height_per_turn_cm /
leds_per_turn * 1.3; // this is the changing height per led.
for (int i = 0; i < num_leds; i++) {
float angle = i * angle_per_led; // angle in radians
float r = radius + 10 + i * height_per_led; // height in cm
// Calculate the x, y coordinates for the corkscrew
float x = r * cos(angle); // x coordinate
float y = r * sin(angle); // y coordinate
// Store the 2D coordinates in the vector
points[i] = vec2f(x, y);
}
FASTLED_WARN("Creating ScreenMap with:\n" << points);
// Create a ScreenMap from the points
fl::ScreenMap screenMap(points.data(), num_leds, .5);
return screenMap;
}
// 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);
corkscrew_args args = corkscrew_args();
fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
fl::ScreenMap screenMap;
CLEDController* addController() {
CLEDController* controller = &FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(leds, NUM_LEDS);
return controller;
}
void setup() {
pinMode(PIN_GRND, OUTPUT);
digitalWrite(PIN_GRND, LOW); // Set ground pin to low
button.addRealButton(Button(PIN_BUTTON));
screenMap = makeScreenMap(args);
//screenMap = ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f);
auto controller = addController();
// Set the screen map for the controller
controller->setScreenMap(screenMap);
// Set power management. This allows this festival stick to conformatable
// run on any USB battery that can output at least 1A at 5V.
// Keep in mind that this sketch is designed to use APA102HD mode, which will
// result in even lowwer run power consumption, since the power mode does not take
// into account the APA102HD gamma correction. However it is still a correct upper bound
// that will match the ledset exactly when the display tries to go full white.
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000);
// set brightness 8
FastLED.setBrightness(brightness.as_int());
button.onChanged([](UIButton& but) {
// This function is called when the button is pressed
// If the button is pressed, show the generative pattern
if (but.isPressed()) {
FASTLED_WARN("Button pressed");
} else {
FASTLED_WARN("NOT Button pressed");
}
});
}
void showGenerative(uint32_t now) {
// This function is called to show the generative pattern
for (int i = 0; i < NUM_LEDS; i++) {
// Get the 2D position of this LED from the screen map
fl::vec3f pos = mapCorkScrew[i];
float x = pos.x;
float y = pos.y;
float z = pos.z;
x*= 20.0f * ledsScale.value();
y*= 20.0f * ledsScale.value();
z*= 20.0f * ledsScale.value();
uint16_t noise_value = inoise16(x,y,z, now / 100);
// Normalize the noise value to 0-255
uint8_t brightness = map(noise_value, 0, 65535, 0, 255);
// Create a hue that changes with position and time
uint8_t sat = int32_t((x * 10 + y * 5 + now / 5)) % 256;
// Set the color
leds[i] = CHSV(170, sat, fl::clamp(255- sat, 64, 255));
}
}
void loop() {
uint32_t now = millis();
fl::clear(leds);
showGenerative(now);
FastLED.show();
}

View File

@@ -0,0 +1,111 @@
/// @file Fire2012.ino
/// @brief Simple one-dimensional fire animation
/// @example Fire2012.ino
#include <FastLED.h>
#define LED_PIN 5
#define COLOR_ORDER GRB
#define CHIPSET WS2811
#define NUM_LEDS 30
#define BRIGHTNESS 200
#define FRAMES_PER_SECOND 60
bool gReverseDirection = false;
CRGB leds[NUM_LEDS];
// Forward declaration
void Fire2012();
void setup() {
delay(3000); // sanity delay
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness( BRIGHTNESS );
}
void loop()
{
// Add entropy to random number generator; we use a lot of it.
random16_add_entropy( random16());
Fire2012(); // run simulation frame
FastLED.show(); // display this frame
FastLED.delay(1000 / FRAMES_PER_SECOND);
}
// Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
////
// This basic one-dimensional 'fire' simulation works roughly as follows:
// There's a underlying array of 'heat' cells, that model the temperature
// at each point along the line. Every cycle through the simulation,
// four steps are performed:
// 1) All cells cool down a little bit, losing heat to the air
// 2) The heat from each cell drifts 'up' and diffuses a little
// 3) Sometimes randomly new 'sparks' of heat are added at the bottom
// 4) The heat from each cell is rendered as a color into the leds array
// The heat-to-color mapping uses a black-body radiation approximation.
//
// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).
//
// This simulation scales it self a bit depending on NUM_LEDS; it should look
// "OK" on anywhere from 20 to 100 LEDs without too much tweaking.
//
// I recommend running this simulation at anywhere from 30-100 frames per second,
// meaning an interframe delay of about 10-35 milliseconds.
//
// Looks best on a high-density LED setup (60+ pixels/meter).
//
//
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 50, suggested range 20-100
#define COOLING 55
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#define SPARKING 120
void Fire2012()
{
// Array of temperature readings at each simulation cell
static uint8_t heat[NUM_LEDS];
// Step 1. Cool down every cell a little
for( int i = 0; i < NUM_LEDS; i++) {
heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for( int k= NUM_LEDS - 1; k >= 2; k--) {
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if( random8() < SPARKING ) {
int y = random8(7);
heat[y] = qadd8( heat[y], random8(160,255) );
}
// Step 4. Map from heat cells to LED colors
for( int j = 0; j < NUM_LEDS; j++) {
CRGB color = HeatColor( heat[j]);
int pixelnumber;
if( gReverseDirection ) {
pixelnumber = (NUM_LEDS-1) - j;
} else {
pixelnumber = j;
}
leds[pixelnumber] = color;
}
}

View File

@@ -0,0 +1,167 @@
/// @file Fire2012WithPalette.ino
/// @brief Simple one-dimensional fire animation with a programmable color palette
/// @example Fire2012WithPalette.ino
#include <Arduino.h>
#include <FastLED.h>
#define LED_PIN 5
#define COLOR_ORDER GRB
#define CHIPSET WS2811
#define NUM_LEDS 30
#define BRIGHTNESS 200
#define FRAMES_PER_SECOND 60
bool gReverseDirection = false;
CRGB leds[NUM_LEDS];
CRGBPalette16 gPal;
// Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
////
// This basic one-dimensional 'fire' simulation works roughly as follows:
// There's a underlying array of 'heat' cells, that model the temperature
// at each point along the line. Every cycle through the simulation,
// four steps are performed:
// 1) All cells cool down a little bit, losing heat to the air
// 2) The heat from each cell drifts 'up' and diffuses a little
// 3) Sometimes randomly new 'sparks' of heat are added at the bottom
// 4) The heat from each cell is rendered as a color into the leds array
// The heat-to-color mapping uses a black-body radiation approximation.
//
// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).
//
// This simulation scales it self a bit depending on NUM_LEDS; it should look
// "OK" on anywhere from 20 to 100 LEDs without too much tweaking.
//
// I recommend running this simulation at anywhere from 30-100 frames per second,
// meaning an interframe delay of about 10-35 milliseconds.
//
// Looks best on a high-density LED setup (60+ pixels/meter).
//
//
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 55, suggested range 20-100
#define COOLING 55
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#define SPARKING 120
void Fire2012WithPalette()
{
// Array of temperature readings at each simulation cell
static uint8_t heat[NUM_LEDS];
// Step 1. Cool down every cell a little
for( int i = 0; i < NUM_LEDS; i++) {
heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for( int k= NUM_LEDS - 1; k >= 2; k--) {
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if( random8() < SPARKING ) {
int y = random8(7);
heat[y] = qadd8( heat[y], random8(160,255) );
}
// Step 4. Map from heat cells to LED colors
for( int j = 0; j < NUM_LEDS; j++) {
// Scale the heat value from 0-255 down to 0-240
// for best results with color palettes.
uint8_t colorindex = scale8( heat[j], 240);
CRGB color = ColorFromPalette( gPal, colorindex);
int pixelnumber;
if( gReverseDirection ) {
pixelnumber = (NUM_LEDS-1) - j;
} else {
pixelnumber = j;
}
leds[pixelnumber] = color;
}
}
// Fire2012 with programmable Color Palette
//
// This code is the same fire simulation as the original "Fire2012",
// but each heat cell's temperature is translated to color through a FastLED
// programmable color palette, instead of through the "HeatColor(...)" function.
//
// Four different static color palettes are provided here, plus one dynamic one.
//
// The three static ones are:
// 1. the FastLED built-in HeatColors_p -- this is the default, and it looks
// pretty much exactly like the original Fire2012.
//
// To use any of the other palettes below, just "uncomment" the corresponding code.
//
// 2. a gradient from black to red to yellow to white, which is
// visually similar to the HeatColors_p, and helps to illustrate
// what the 'heat colors' palette is actually doing,
// 3. a similar gradient, but in blue colors rather than red ones,
// i.e. from black to blue to aqua to white, which results in
// an "icy blue" fire effect,
// 4. a simplified three-step gradient, from black to red to white, just to show
// that these gradients need not have four components; two or
// three are possible, too, even if they don't look quite as nice for fire.
//
// The dynamic palette shows how you can change the basic 'hue' of the
// color palette every time through the loop, producing "rainbow fire".
void setup() {
delay(3000); // sanity delay
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness( BRIGHTNESS );
// This first palette is the basic 'black body radiation' colors,
// which run from black to red to bright yellow to white.
gPal = HeatColors_p;
// These are other ways to set up the color palette for the 'fire'.
// First, a gradient from black to red to yellow to white -- similar to HeatColors_p
// gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White);
// Second, this palette is like the heat colors, but blue/aqua instead of red/yellow
// gPal = CRGBPalette16( CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White);
// Third, here's a simpler, three-step gradient, from black to red to white
// gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::White);
Serial.println("setup");
}
void loop()
{
// Add entropy to random number generator; we use a lot of it.
random16_add_entropy( random16());
// Fourth, the most sophisticated: this one sets up a new palette every
// time through the loop, based on a hue that changes every time.
// The palette is a gradient from black, to a dark color based on the hue,
// to a light color based on the hue, to white.
//
// static uint8_t hue = 0;
// hue++;
// CRGB darkcolor = CHSV(hue,255,192); // pure hue, three-quarters brightness
// CRGB lightcolor = CHSV(hue,128,255); // half 'whitened', full brightness
// gPal = CRGBPalette16( CRGB::Black, darkcolor, lightcolor, CRGB::White);
Fire2012WithPalette(); // run simulation frame, using palette colors
FastLED.show(); // display this frame
FastLED.delay(1000 / FRAMES_PER_SECOND);
}

View File

@@ -0,0 +1,262 @@
/// @file Fire2023.ino
/// @brief Enhanced fire effect with ScreenMap
/// @example Fire2023.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 is a fire effect based on the famous Fire2012; but with various small improvements.
Perlin noise is being used to make a fire layer and a smoke layer;
and the overlay of both can make a quite realistic effect.
The speed of both need to be adapted to the matrix size and width:
* Super small matrices (like 3x3 led) don't need the smoke
* medium sized matrices (8x8 for example) profit from fine tuning both Fire Speed/scale as well as Smoke speed/scale
This code was adapted for a matrix with just four LED columns in 90° around a core and a height of 28.
Right at the bottom of the code, you find a translation matrix that needs to be adapted to your set up. I included
a link to a helpful page for this.
@repo https://github.com/Anderas2/Fire2023
@author https://github.com/Anderas2
Demo: https://www.youtube.com/shorts/a_Wr0q9YQs4
*/
#include "FastLED.h"
#include "fl/xymap.h"
#include "fl/screenmap.h"
#include "fl/vector.h"
using namespace fl;
// matrix size
#define WIDTH 4
#define HEIGHT 28
#define CentreX (WIDTH / 2) - 1
#define CentreY (HEIGHT / 2) - 1
// NUM_LEDS = WIDTH * HEIGHT
#define PIXELPIN 3 // universal pin that works on all platforms
#define NUM_LEDS 120
#define LAST_VISIBLE_LED 119
// Fire properties
#define BRIGHTNESS 255
#define FIRESPEED 17
#define FLAMEHEIGHT 3.8 // the higher the value, the higher the flame
#define FIRENOISESCALE 125 // small values, softer fire. Big values, blink fire. 0-255
// Smoke screen properties
// The smoke screen works best for big fire effects. It effectively cuts of a part of the flames
// from the rest, sometimes; which looks very much fire-like. For small fire effects with low
// LED count in the height, it doesn't help
// speed must be a little different and faster from Firespeed, to be visible.
// Dimmer should be somewhere in the middle for big fires, and low for small fires.
#define SMOKESPEED 25 // how fast the perlin noise is parsed for the smoke
#define SMOKENOISE_DIMMER 250 // thickness of smoke: the lower the value, the brighter the flames. 0-255
#define SMOKENOISESCALE 125 // small values, softer smoke. Big values, blink smoke. 0-255
CRGB leds[NUM_LEDS];
// fire palette roughly like matlab "hot" colormap
// This was one of the most important parts to improve - fire color makes fire impression.
// position, r, g, b value.
// max value for "position" is BRIGHTNESS
DEFINE_GRADIENT_PALETTE(hot_gp) {
27, 0, 0, 0, // black
28, 140, 40, 0, // red
30, 205, 80, 0, // orange
155, 255, 100, 0,
210, 255, 200, 0, // yellow
255, 255, 255, 255 // white
};
CRGBPalette32 hotPalette = hot_gp;
// Map XY coordinates to numbers on the LED strip
uint8_t XY (uint8_t x, uint8_t y);
// parameters and buffer for the noise array
#define NUM_LAYERS 2
// two layers of perlin noise make the fire effect
#define FIRENOISE 0
#define SMOKENOISE 1
uint32_t x[NUM_LAYERS];
uint32_t y[NUM_LAYERS];
uint32_t z[NUM_LAYERS];
uint32_t scale_x[NUM_LAYERS];
uint32_t scale_y[NUM_LAYERS];
uint8_t noise[NUM_LAYERS][WIDTH][HEIGHT];
uint8_t noise2[NUM_LAYERS][WIDTH][HEIGHT];
uint8_t heat[NUM_LEDS];
ScreenMap makeScreenMap();
void setup() {
//Serial.begin(115200);
// Adjust this for you own setup. Use the hardware SPI pins if possible.
// On Teensy 3.1/3.2 the pins are 11 & 13
// Details here: https://github.com/FastLED/FastLED/wiki/SPI-Hardware-or-Bit-banging
// In case you see flickering / glitching leds, reduce the data rate to 12 MHZ or less
auto screenMap = makeScreenMap();
FastLED.addLeds<NEOPIXEL, PIXELPIN>(leds, NUM_LEDS).setScreenMap(screenMap); // Pin für Neopixel
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(DISABLE_DITHER);
}
void Fire2023(uint32_t now);
void loop() {
EVERY_N_MILLISECONDS(8) {
Fire2023(millis());
}
FastLED.show();
}
ScreenMap makeScreenMap() {
fl::vector<vec2f> lut;
for (uint16_t y = 0; y < WIDTH; y++) {
for (uint16_t x = 0; x < HEIGHT; x++) {
vec2f xy = {float(x) * 3, float(y) * 20};
lut.push_back(xy);
}
}
return ScreenMap(lut.data(), lut.size(), 1);
}
void Fire2023(uint32_t now) {
// some changing values
// these values are produced by perlin noise to add randomness and smooth transitions
uint16_t ctrl1 = inoise16(11 * now, 0, 0);
uint16_t ctrl2 = inoise16(13 * now, 100000, 100000);
uint16_t ctrl = ((ctrl1 + ctrl2) >> 1);
// parameters for the fire heat map
x[FIRENOISE] = 3 * ctrl * FIRESPEED;
y[FIRENOISE] = 20 * now * FIRESPEED;
z[FIRENOISE] = 5 * now * FIRESPEED;
scale_x[FIRENOISE] = scale8(ctrl1, FIRENOISESCALE);
scale_y[FIRENOISE] = scale8(ctrl2, FIRENOISESCALE);
//calculate the perlin noise data for the fire
for (uint8_t x_count = 0; x_count < WIDTH; x_count++) {
uint32_t xoffset = scale_x[FIRENOISE] * (x_count - CentreX);
for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
uint32_t yoffset = scale_y[FIRENOISE] * (y_count - CentreY);
uint16_t data = ((inoise16(x[FIRENOISE] + xoffset, y[FIRENOISE] + yoffset, z[FIRENOISE])) + 1);
noise[FIRENOISE][x_count][y_count] = data >> 8;
}
}
// parameters for the smoke map
x[SMOKENOISE] = 3 * ctrl * SMOKESPEED;
y[SMOKENOISE] = 20 * now * SMOKESPEED;
z[SMOKENOISE] = 5 * now * SMOKESPEED;
scale_x[SMOKENOISE] = scale8(ctrl1, SMOKENOISESCALE);
scale_y[SMOKENOISE] = scale8(ctrl2, SMOKENOISESCALE);
//calculate the perlin noise data for the smoke
for (uint8_t x_count = 0; x_count < WIDTH; x_count++) {
uint32_t xoffset = scale_x[SMOKENOISE] * (x_count - CentreX);
for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
uint32_t yoffset = scale_y[SMOKENOISE] * (y_count - CentreY);
uint16_t data = ((inoise16(x[SMOKENOISE] + xoffset, y[SMOKENOISE] + yoffset, z[SMOKENOISE])) + 1);
noise[SMOKENOISE][x_count][y_count] = data / SMOKENOISE_DIMMER;
}
}
//copy everything one line up
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, y)] = heat[XY(x, y + 1)];
}
}
// draw lowest line - seed the fire where it is brightest and hottest
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, HEIGHT-1)] = noise[FIRENOISE][WIDTH - x][CentreX];
//if (heat[XY(x, HEIGHT-1)] < 200) heat[XY(x, HEIGHT-1)] = 150;
}
// dim the flames based on FIRENOISE noise.
// if the FIRENOISE noise is strong, the led goes out fast
// if the FIRENOISE noise is weak, the led stays on stronger.
// once the heat is gone, it stays dark.
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
uint8_t dim = noise[FIRENOISE][x][y];
// high value in FLAMEHEIGHT = less dimming = high flames
dim = dim / FLAMEHEIGHT;
dim = 255 - dim;
heat[XY(x, y)] = scale8(heat[XY(x, y)] , dim);
// map the colors based on heatmap
// use the heat map to set the color of the LED from the "hot" palette
// whichpalette position brightness blend or not
leds[XY(x, y)] = ColorFromPalette(hotPalette, heat[XY(x, y)], heat[XY(x, y)], LINEARBLEND);
// dim the result based on SMOKENOISE noise
// this is not saved in the heat map - the flame may dim away and come back
// next iteration.
leds[XY(x, y)].nscale8(noise[SMOKENOISE][x][y]);
}
}
}
/* Physical layout of LED strip ****************************/
uint8_t XY (uint8_t x, uint8_t y) {
// any out of bounds address maps to the first hidden pixel
// https://macetech.github.io/FastLED-XY-Map-Generator/
if ( (x >= WIDTH) || (y >= HEIGHT) ) {
return (LAST_VISIBLE_LED + 1);
}
const uint8_t XYTable[] = {
25, 26, 81, 82,
25, 27, 81, 83,
25, 28, 80, 84,
24, 29, 79, 85,
23, 30, 78, 86,
22, 31, 77, 87,
21, 32, 76, 88,
20, 33, 75, 89,
19, 34, 74, 90,
18, 35, 73, 91,
17, 36, 72, 92,
16, 37, 71, 93,
15, 38, 70, 94,
14, 39, 69, 95,
13, 40, 68, 96,
12, 41, 67, 97,
11, 42, 66, 98,
10, 43, 65, 99,
9, 44, 64, 100,
8, 45, 63, 101,
7, 46, 62, 102,
6, 47, 61, 103,
5, 48, 60, 104,
4, 49, 59, 105,
3, 50, 58, 106,
2, 51, 57, 107,
1, 52, 56, 108,
0, 53, 55, 109
};
uint8_t i = (y * WIDTH) + x;
uint8_t j = XYTable[i];
return j;
}

View File

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

View File

@@ -0,0 +1,215 @@
/*
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 sketch creates a fire effect on a cylindrical LED display using Perlin noise.
Unlike a flat matrix, this cylinder connects the left and right edges (x=0 and x=width-1),
creating a seamless wrap-around effect. The fire appears to rise from the bottom,
with colors transitioning from black to red/yellow/white (or other palettes).
*/
// Perlin noise fire procedure
// 16x16 rgb led cylinder demo
// Exactly the same as the FireMatrix example, but with a cylinder, meaning that the x=0
// and x = len-1 are connected.
// This also showcases the inoise16(x,y,z,t) function which handles 3D+time noise effects.
// Keep in mind that a cylinder is embedded in a 3D space with time being used to add
// additional noise to the effect.
// HOW THE CYLINDRICAL FIRE EFFECT WORKS:
// 1. We use sine and cosine to map the x-coordinate to a circle in 3D space
// 2. This creates a cylindrical mapping where the left and right edges connect seamlessly
// 3. We use 4D Perlin noise (x,y,z,t) to generate natural-looking fire patterns
// 4. The height coordinate controls color fade-out to create the rising fire effect
// 5. The time dimension adds continuous variation to make the fire look dynamic
#include "FastLED.h" // Main FastLED library for controlling LEDs
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, buttons, etc.)
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
#include "fx/time.h" // Time manipulation utilities for animations
using namespace fl; // Use the FastLED namespace for convenience
// Cylinder dimensions - this defines the size of our virtual LED grid
#define HEIGHT 100 // Number of rows in the cylinder (vertical dimension)
#define WIDTH 100 // Number of columns in the cylinder (circumference)
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
// UI elements that appear in the FastLED web compiler interface:
UITitle title("FireCylinder Demo"); // Title displayed in the UI
UIDescription description("This Fire demo wraps around the cylinder. It uses Perlin noise to create a fire effect.");
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
// UI Controls for adjusting the fire effect:
UISlider scaleXY("Scale", 8, 1, 100, 1); // Controls the overall size of the fire pattern
UISlider speedY("SpeedY", 1.3, 1, 6, .1); // Controls how fast the fire moves upward
UISlider scaleX("ScaleX", .3, 0.1, 3, .01); // Controls the horizontal scale (affects the wrap-around)
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
// Array to hold all LED color values - one CRGB struct per LED
CRGB leds[HEIGHT * WIDTH];
// Color palettes define the gradient of colors used for the fire effect
// Each entry has the format: position (0-255), R, G, B
DEFINE_GRADIENT_PALETTE(firepal){
// Traditional fire palette - transitions from black to red to yellow to white
0, 0, 0, 0, // black (bottom of fire)
32, 255, 0, 0, // red (base of flames)
190, 255, 255, 0, // yellow (middle of flames)
255, 255, 255, 255 // white (hottest part/tips of flames)
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
// Green fire palette - for a toxic/alien look
0, 0, 0, 0, // black (bottom)
32, 0, 70, 0, // dark green (base)
190, 57, 255, 20, // electric neon green (middle)
255, 255, 255, 255 // white (hottest part)
};
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
// Blue fire palette - for a cold/ice fire look
0, 0, 0, 0, // Black (bottom)
32, 0, 0, 70, // Dark blue (base)
128, 20, 57, 255, // Electric blue (middle)
255, 255, 255, 255 // White (hottest part)
};
// Create a mapping between 1D array positions and 2D x,y coordinates
XYMap xyMap(WIDTH, HEIGHT, SERPENTINE);
void setup() {
Serial.begin(115200); // Initialize serial communication for debugging
// Initialize the LED strip:
// - NEOPIXEL is the LED type
// - 3 is the data pin number (for real hardware)
// - setScreenMap connects our 2D coordinate system to the 1D LED array
fl::ScreenMap screen_map = xyMap.toScreenMap();
screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED)
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(screen_map);
// Apply color correction for more accurate colors on LED strips
FastLED.setCorrection(TypicalLEDStrip);
}
uint8_t getPaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height,
uint32_t y_speed) {
// This function calculates which color to use from our palette for each LED
// Get the scale factor from the UI slider
uint16_t scale = scaleXY.as<uint16_t>();
// Convert width position to an angle (0-255 represents 0-360 degrees)
// This maps our flat coordinate to a position on a cylinder
float xf = (float)width / (float)max_width; // Normalized position (0.0 to 1.0)
uint8_t x = (uint8_t)(xf * 255); // Convert to 0-255 range for trig functions
// Calculate the sine and cosine of this angle to get 3D coordinates on the cylinder
uint32_t cosx = cos8(x); // cos8 returns a value 0-255 representing cosine
uint32_t sinx = sin8(x); // sin8 returns a value 0-255 representing sine
// Apply scaling to the sine/cosine values
// This controls how "wide" the noise pattern is around the cylinder
float trig_scale = scale * scaleX.value();
cosx *= trig_scale;
sinx *= trig_scale;
// Calculate Y coordinate (vertical position) with speed offset for movement
uint32_t y = height * scale + y_speed;
// Calculate Z coordinate (time dimension) - controls how the pattern changes over time
uint16_t z = millis32 / invSpeedZ.as<uint16_t>();
FL_UNUSED(z); // Suppress unused variable warning
// Generate 16-bit Perlin noise using our 4D coordinates (x,y,z,t)
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
// The last parameter (0) could be replaced with another time variable for more variation
uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, 0);
// Convert 16-bit noise to 8-bit by taking the high byte
uint8_t noise_val = noise16 >> 8;
// Calculate how much to subtract based on vertical position (height)
// This creates the fade-out effect from bottom to top
// The formula maps height from 0 to max_height-1 to a value from 255 to 0
int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 /
(max_height - 1);
// Subtract the factor from the noise value (with underflow protection)
// qsub8 is a "saturating subtraction" - it won't go below 0
return qsub8(noise_val, subtraction_factor);
}
CRGBPalette16 getPalette() {
// This function returns the appropriate color palette based on the UI selection
switch (palette) {
case 0:
return firepal; // Traditional orange/red fire
case 1:
return electricGreenFirePal; // Green "toxic" fire
case 2:
return electricBlueFirePal; // Blue "cold" fire
default:
return firepal; // Default to traditional fire if invalid value
}
}
void loop() {
// The main program loop that runs continuously
// Set the overall brightness from the UI slider
FastLED.setBrightness(brightness);
// Get the selected color palette
CRGBPalette16 myPal = getPalette();
// Get the current time in milliseconds
uint32_t now = millis();
// Update the animation speed from the UI slider
timeScale.setSpeed(speedY);
// Calculate the current y-offset for animation (makes the fire move)
uint32_t y_speed = timeScale.update(now);
// Loop through every LED in our cylindrical matrix
for (int width = 0; width < WIDTH; width++) {
for (int height = 0; height < HEIGHT; height++) {
// Calculate which color to use from our palette for this LED
// This function handles the cylindrical mapping using sine/cosine
uint8_t palette_index =
getPaletteIndex(now, width, WIDTH, height, HEIGHT, y_speed);
// Get the actual RGB color from the palette
// BRIGHTNESS ensures we use the full brightness range
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
// Convert our 2D coordinates to the 1D array index
// We use (WIDTH-1)-width and (HEIGHT-1)-height to flip the coordinates
// This makes the fire appear to rise from the bottom
int index = xyMap((WIDTH - 1) - width, (HEIGHT - 1) - height);
// Set the LED color in our array
leds[index] = c;
}
}
// Send the color data to the actual LEDs
FastLED.show();
}

View File

@@ -0,0 +1,9 @@
#include "FastLED.h" // Main FastLED library for controlling LEDs
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#include "FireCylinder.h"
#endif

View File

@@ -0,0 +1,200 @@
/*
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 sketch creates a fire effect using Perlin noise on a matrix of LEDs.
The fire appears to move upward with colors transitioning from black at the bottom
to white at the top, with red and yellow in between (for the default palette).
*/
// Perlin noise fire procedure
// 16x16 rgb led matrix demo
// Yaroslaw Turbin, 22.06.2020
// https://vk.com/ldirko
// https://www.reddit.com/user/ldirko/
// https://www.reddit.com/r/FastLED/comments/hgu16i/my_fire_effect_implementation_based_on_perlin/
// Based on the code found at: https://editor.soulmatelights.com/gallery/1229-
// HOW THE FIRE EFFECT WORKS:
// 1. We use Perlin noise with time offset for X and Z coordinates
// to create a naturally scrolling fire pattern that changes over time
// 2. We distort the fire noise to make it look more realistic
// 3. We subtract a value based on Y coordinate to shift the fire color in the palette
// (not just brightness). This creates a fade-out effect from the bottom of the matrix to the top
// 4. The palette is carefully designed to give realistic fire colors
#if defined(__AVR__)
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#include "FastLED.h" // Main FastLED library for controlling LEDs
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, etc.)
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
#include "fx/time.h" // Time manipulation utilities
using namespace fl; // Use the FastLED namespace for convenience
// Matrix dimensions - this defines the size of our virtual LED grid
#define HEIGHT 100 // Number of rows in the matrix
#define WIDTH 100 // Number of columns in the matrix
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
// UI Controls that appear in the FastLED web compiler interface:
UISlider scaleXY("Scale", 20, 1, 100, 1); // Controls the size of the fire pattern
UISlider speedY("SpeedY", 1, 1, 6, .1); // Controls how fast the fire moves upward
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
// Array to hold all LED color values - one CRGB struct per LED
CRGB leds[HEIGHT * WIDTH];
// Color palettes define the gradient of colors used for the fire effect
// Each entry has the format: position (0-255), R, G, B
DEFINE_GRADIENT_PALETTE(firepal){
// Traditional fire palette - transitions from black to red to yellow to white
0, 0, 0, 0, // black (bottom of fire)
32, 255, 0, 0, // red (base of flames)
190, 255, 255, 0, // yellow (middle of flames)
255, 255, 255, 255 // white (hottest part/tips of flames)
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
// Green fire palette - for a toxic/alien look
0, 0, 0, 0, // black (bottom)
32, 0, 70, 0, // dark green (base)
190, 57, 255, 20, // electric neon green (middle)
255, 255, 255, 255 // white (hottest part)
};
DEFINE_GRADIENT_PALETTE(electricBlueFirePal) {
// Blue fire palette - for a cold/ice fire look
0, 0, 0, 0, // Black (bottom)
32, 0, 0, 70, // Dark blue (base)
128, 20, 57, 255, // Electric blue (middle)
255, 255, 255, 255 // White (hottest part)
};
// Create a mapping between 1D array positions and 2D x,y coordinates
XYMap xyMap(WIDTH, HEIGHT, SERPENTINE);
void setup() {
Serial.begin(115200); // Initialize serial communication for debugging
// Initialize the LED strip:
// - NEOPIXEL is the LED type
// - 3 is the data pin number (for real hardware)
// - setScreenMap connects our 2D coordinate system to the 1D LED array
fl::ScreenMap screen_map = xyMap.toScreenMap();
screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED)
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(screen_map);
// Apply color correction for more accurate colors on LED strips
FastLED.setCorrection(TypicalLEDStrip);
}
uint8_t getPaletteIndex(uint32_t millis32, int i, int j, uint32_t y_speed) {
// This function calculates which color to use from our palette for each LED
// Get the scale factor from the UI slider (controls the "size" of the fire)
uint16_t scale = scaleXY.as<uint16_t>();
// Calculate 3D coordinates for the Perlin noise function:
uint16_t x = i * scale; // X position (horizontal in matrix)
uint32_t y = j * scale + y_speed; // Y position (vertical) + movement offset
uint16_t z = millis32 / invSpeedZ.as<uint16_t>(); // Z position (time dimension)
// Generate 16-bit Perlin noise value using these coordinates
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
uint16_t noise16 = inoise16(x << 8, y << 8, z << 8);
// Convert 16-bit noise to 8-bit by taking the high byte (>> 8 shifts right by 8 bits)
uint8_t noise_val = noise16 >> 8;
// Calculate how much to subtract based on vertical position (j)
// This creates the fade-out effect from bottom to top
// abs8() ensures we get a positive value
// The formula maps j from 0 to HEIGHT-1 to a value from 255 to 0
int8_t subtraction_factor = abs8(j - (HEIGHT - 1)) * 255 / (HEIGHT - 1);
// Subtract the factor from the noise value (with underflow protection)
// qsub8 is a "saturating subtraction" - it won't go below 0
return qsub8(noise_val, subtraction_factor);
}
CRGBPalette16 getPalette() {
// This function returns the appropriate color palette based on the UI selection
switch (palette) {
case 0:
return firepal; // Traditional orange/red fire
case 1:
return electricGreenFirePal; // Green "toxic" fire
case 2:
return electricBlueFirePal; // Blue "cold" fire
default:
return firepal; // Default to traditional fire if invalid value
}
}
void loop() {
// The main program loop that runs continuously
// Set the overall brightness from the UI slider
FastLED.setBrightness(brightness);
// Get the selected color palette
CRGBPalette16 myPal = getPalette();
// Get the current time in milliseconds
uint32_t now = millis();
// Update the animation speed from the UI slider
timeScale.setSpeed(speedY);
// Calculate the current y-offset for animation (makes the fire move)
uint32_t y_speed = timeScale.update(now);
// Loop through every LED in our matrix
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
// Calculate which color to use from our palette for this LED
uint8_t palette_index = getPaletteIndex(now, i, j, y_speed);
// Get the actual RGB color from the palette
// BRIGHTNESS ensures we use the full brightness range
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
// Convert our 2D coordinates (i,j) to the 1D array index
// We use (WIDTH-1)-i and (HEIGHT-1)-j to flip the coordinates
// This makes the fire appear to rise from the bottom
int index = xyMap((WIDTH - 1) - i, (HEIGHT - 1) - j);
// Set the LED color in our array
leds[index] = c;
}
}
// Send the color data to the actual LEDs
FastLED.show();
}
#endif

View File

@@ -0,0 +1,9 @@
#include "FastLED.h" // Main FastLED library for controlling LEDs
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#include "FireMatrix.h"
#endif

View File

@@ -0,0 +1,96 @@
/// @file FirstLight.ino
/// @brief Animate a white dot moving along a strip of LEDs
/// @example FirstLight.ino
// Use if you want to force the software SPI subsystem to be used for some reason (generally, you don't)
// #define FASTLED_FORCE_SOFTWARE_SPI
// Use if you want to force non-accelerated pin access (hint: you really don't, it breaks lots of things)
// #define FASTLED_FORCE_SOFTWARE_SPI
// #define FASTLED_FORCE_SOFTWARE_PINS
#include <FastLED.h>
///////////////////////////////////////////////////////////////////////////////////////////
//
// Move a white dot along the strip of leds. This program simply shows how to configure the leds,
// and then how to turn a single pixel white and then off, moving down the line of pixels.
//
// How many leds are in the strip?
#define NUM_LEDS 60
// 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
// This is an array of leds. One item for each led in your strip.
CRGB leds[NUM_LEDS];
// This function sets up the ledsand tells the controller about them
void setup() {
// sanity check delay - allows reprogramming if accidently blowing power w/leds
delay(2000);
// 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
// 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
}
// This function runs over and over, and is where you do the magic to light
// your leds.
void loop() {
// Move a single white led
for(int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed = whiteLed + 1) {
// Turn our current led on to white, then show the leds
leds[whiteLed] = CRGB::White;
// Show the leds (only one of which is set to white, from above)
FastLED.show();
// Wait a little bit
delay(100);
// Turn our current led back to black for the next loop around
leds[whiteLed] = CRGB::Black;
}
}

View File

@@ -0,0 +1,41 @@
/// @file FxCylon.ino
/// @brief Cylon eye effect with ScreenMap
/// @example FxCylon.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 "fx/1d/cylon.h"
#include "fl/screenmap.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).
#define DATA_PIN 2
// Define the array of leds
CRGB leds[NUM_LEDS];
// Create a Cylon instance
Cylon cylon(NUM_LEDS);
void setup() {
ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.5f);
FastLED.addLeds<WS2812,DATA_PIN,RGB>(leds,NUM_LEDS).setRgbw().setScreenMap(screenMap);
FastLED.setBrightness(84);
}
void loop() {
cylon.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
delay(cylon.delay_ms);
}

View File

@@ -0,0 +1,70 @@
/// @file FxDemoReel100.ino
/// @brief DemoReel100 effects collection with ScreenMap
/// @example FxDemoReel100.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 "fx/1d/demoreel100.h"
#include "fl/screenmap.h"
#include "defs.h" // for NUM_LEDS
#if !HAS_ENOUGH_MEMORY
void setup() {}
void loop() {}
#else
using namespace fl;
#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
#define USES_RGBW 0
#if USES_RGBW
Rgbw rgbwMode = RgbwDefault();
#else
Rgbw rgbwMode = RgbwInvalid(); // No RGBW mode, just use RGB.
#endif
DemoReel100Ptr demoReel = fl::make_shared<DemoReel100>(NUM_LEDS);
void setup() {
ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS);
// tell FastLED about the LED strip configuration
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS, 2.0f)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screenMap)
.setRgbw(rgbwMode);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
void loop()
{
// Run the DemoReel100 draw function
demoReel->draw(Fx::DrawContext(millis(), leds));
// 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);
}
#endif // HAS_ENOUGH_MEMORY

View File

@@ -0,0 +1,8 @@
#pragma once
// if attiny85 or attiny88, use less leds so this example can compile.
#if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny88__)
#define HAS_ENOUGH_MEMORY 0
#else
#define HAS_ENOUGH_MEMORY 1
#endif

View File

@@ -0,0 +1,79 @@
/// @file FxEngine.ino
/// @brief Demonstrates FxEngine for switching between effects
/// @example FxEngine.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>
using namespace fl;
#if defined(__AVR__)
// __AVR__: Not enough memory enough for the FxEngine, so skipping this example
void setup() {}
void loop() {}
#else
#include "fx/2d/noisepalette.h"
#include "fx/2d/animartrix.hpp"
#include "fx/fx_engine.h"
#include "fl/ui.h"
#define LED_PIN 2
#define BRIGHTNESS 96
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define MATRIX_WIDTH 22
#define MATRIX_HEIGHT 22
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
#ifdef __EMSCRIPTEN__
#define IS_SERPINTINE false
#else
#define IS_SERPINTINE true
#endif
UISlider SCALE("SCALE", 20, 20, 100);
UISlider SPEED("SPEED", 30, 20, 100);
CRGB leds[NUM_LEDS];
XYMap xyMap(MATRIX_WIDTH, MATRIX_HEIGHT, IS_SERPINTINE); // No serpentine
NoisePalette noisePalette1(xyMap);
NoisePalette noisePalette2(xyMap);
FxEngine fxEngine(NUM_LEDS);
UICheckbox switchFx("Switch Fx", true);
void setup() {
delay(1000); // sanity delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(MATRIX_WIDTH, MATRIX_HEIGHT);
FastLED.setBrightness(96);
noisePalette1.setPalettePreset(2);
noisePalette2.setPalettePreset(4);
fxEngine.addFx(noisePalette1);
fxEngine.addFx(noisePalette2);
}
void loop() {
noisePalette1.setSpeed(SPEED);
noisePalette1.setScale(SCALE);
noisePalette2.setSpeed(SPEED);
noisePalette2.setScale(int(SCALE) * 3 / 2); // Make the different.
EVERY_N_SECONDS(1) {
if (switchFx) {
fxEngine.nextFx(500);
}
}
fxEngine.draw(millis(), leds);
FastLED.show();
}
#endif // __AVR__

View File

@@ -0,0 +1,83 @@
/// @file FxFire2012.ino
/// @brief Fire2012 effect with ScreenMap
/// @example FxFire2012.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.
/// @brief Simple one-dimensional fire animation function
// Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
////
// This basic one-dimensional 'fire' simulation works roughly as follows:
// There's a underlying array of 'heat' cells, that model the temperature
// at each point along the line. Every cycle through the simulation,
// four steps are performed:
// 1) All cells cool down a little bit, losing heat to the air
// 2) The heat from each cell drifts 'up' and diffuses a little
// 3) Sometimes randomly new 'sparks' of heat are added at the bottom
// 4) The heat from each cell is rendered as a color into the leds array
// The heat-to-color mapping uses a black-body radiation approximation.
//
// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).
//
// This simulation scales it self a bit depending on NUM_LEDS; it should look
// "OK" on anywhere from 20 to 100 LEDs without too much tweaking.
//
// I recommend running this simulation at anywhere from 30-100 frames per second,
// meaning an interframe delay of about 10-35 milliseconds.
//
// Looks best on a high-density LED setup (60+ pixels/meter).
//
//
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 50, suggested range 20-100
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#include <FastLED.h>
#include "fx/1d/fire2012.h"
#include "fl/screenmap.h"
using namespace fl;
#define LED_PIN 5
#define COLOR_ORDER GRB
#define CHIPSET WS2811
#define NUM_LEDS 92
#define BRIGHTNESS 128
#define FRAMES_PER_SECOND 30
#define COOLING 55
#define SPARKING 120
#define REVERSE_DIRECTION false
CRGB leds[NUM_LEDS];
Fire2012Ptr fire = fl::make_shared<Fire2012>(NUM_LEDS, COOLING, SPARKING, REVERSE_DIRECTION);
void setup() {
ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5, .4);
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screenMap)
.setRgbw();
FastLED.setBrightness(BRIGHTNESS);
}
void loop()
{
fire->draw(Fx::DrawContext(millis(), leds)); // run simulation frame
FastLED.show(millis()); // display this frame
FastLED.delay(1000 / FRAMES_PER_SECOND);
}

View File

@@ -0,0 +1,107 @@
/// @file FxGfx2Video.ino
/// @brief Demonstrates graphics to video conversion
/// @example FxGfx2Video.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.
/// @file Gfx2Video.ino
/// @brief Demonstrates drawing to a frame buffer, which is used as source video for the memory player.
/// The render pattern is alternating black/red pixels as a checkerboard.
/// @example VideoTest.ino
#ifndef COMPILE_VIDEO_STREAM
#if defined(__AVR__)
// This has grown too large for the AVR to handle.
#define COMPILE_VIDEO_STREAM 0
#else
#define COMPILE_VIDEO_STREAM 1
#endif
#endif // COMPILE_VIDEO_STREAM
#if COMPILE_VIDEO_STREAM
#include <FastLED.h>
#include "fl/bytestreammemory.h"
#include "fx/fx_engine.h"
#include "fl/memory.h"
#include "fx/video.h"
#include "fl/dbg.h"
using namespace fl;
#define LED_PIN 2
#define BRIGHTNESS 96
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define MATRIX_WIDTH 22
#define MATRIX_HEIGHT 22
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
CRGB leds[NUM_LEDS];
const int BYTES_PER_FRAME = 3 * NUM_LEDS;
const int NUM_FRAMES = 2;
const uint32_t BUFFER_SIZE = BYTES_PER_FRAME * NUM_FRAMES;
ByteStreamMemoryPtr memoryStream;
FxEngine fxEngine(NUM_LEDS);
// Create and initialize Video object
XYMap xymap(MATRIX_WIDTH, MATRIX_HEIGHT);
Video video(NUM_LEDS, 2.0f);
void write_one_frame(ByteStreamMemoryPtr memoryStream) {
//memoryStream->seek(0); // Reset to the beginning of the stream
uint32_t total_bytes_written = 0;
bool toggle = (millis() / 500) % 2 == 0;
FASTLED_DBG("Writing frame data, toggle = " << toggle);
for (uint32_t i = 0; i < NUM_LEDS; ++i) {
CRGB color = (toggle ^ i%2) ? CRGB::Black : CRGB::Red;
size_t bytes_written = memoryStream->writeCRGB(&color, 1);
if (bytes_written != 1) {
FASTLED_DBG("Failed to write frame data, wrote " << bytes_written << " bytes");
break;
}
total_bytes_written += bytes_written;
}
if (total_bytes_written) {
FASTLED_DBG("Frame written, total bytes: " << total_bytes_written);
}
}
void setup() {
delay(1000); // sanity delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(xymap);
FastLED.setBrightness(BRIGHTNESS);
// Create and fill the ByteStreamMemory with test data
memoryStream = fl::make_shared<ByteStreamMemory>(BUFFER_SIZE*sizeof(CRGB));
video.beginStream(memoryStream);
// Add the video effect to the FxEngine
fxEngine.addFx(video);
}
void loop() {
EVERY_N_MILLISECONDS(500) {
write_one_frame(memoryStream); // Write next frame data
}
// write_one_frame(memoryStream); // Write next frame data
// Draw the frame
fxEngine.draw(millis(), leds);
// Show the LEDs
FastLED.show();
delay(20); // Adjust this delay to control frame rate
}
#else
void setup() {}
void loop() {}
#endif // COMPILE_VIDEO_STREAM

View File

@@ -0,0 +1,101 @@
/// @file FxNoisePlusPalette.ino
/// @brief Noise plus palette effect with XYMap
/// @example FxNoisePlusPalette.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>
#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 "fx/2d/noisepalette.h"
#include "fl/ui.h"
using namespace fl;
#define LED_PIN 3
#define BRIGHTNESS 96
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define MATRIX_WIDTH 16
#define MATRIX_HEIGHT 16
#if __EMSCRIPTEN__
#define GRID_SERPENTINE 0
#else
#define GRID_SERPENTINE 1
#endif
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
// This example combines two features of FastLED to produce a remarkable range
// of effects from a relatively small amount of code. This example combines
// FastLED's color palette lookup functions with FastLED's Perlin noise
// generator, and the combination is extremely powerful.
//
// You might want to look at the "ColorPalette" and "Noise" examples separately
// if this example code seems daunting.
//
//
// The basic setup here is that for each frame, we generate a new array of
// 'noise' data, and then map it onto the LED matrix through a color palette.
//
// Periodically, the color palette is changed, and new noise-generation
// parameters are chosen at the same time. In this example, specific
// noise-generation values have been selected to match the given color palettes;
// some are faster, or slower, or larger, or smaller than others, but there's no
// reason these parameters can't be freely mixed-and-matched.
//
// In addition, this example includes some fast automatic 'data smoothing' at
// lower noise speeds to help produce smoother animations in those cases.
//
// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are
// used, as well as some 'hand-defined' ones, and some proceedurally generated
// palettes.
// Scale determines how far apart the pixels in our noise matrix are. Try
// changing these values around to see how it affects the motion of the display.
// The higher the value of scale, the more "zoomed out" the noise iwll be. A
// value of 1 will be so zoomed in, you'll mostly see solid colors.
UISlider SCALE("SCALE", 20, 1, 100, 1);
// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll
// use the z-axis for "time". speed determines how fast time moves forward. Try
// 1 for a very slow moving effect, or 60 for something that ends up looking
// like water.
UISlider SPEED("SPEED", 30, 1, 60, 1);
CRGB leds[NUM_LEDS];
XYMap xyMap(MATRIX_WIDTH, MATRIX_HEIGHT, GRID_SERPENTINE);
NoisePalette noisePalette(xyMap);
void setup() {
delay(1000); // sanity delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip);
FastLED.setBrightness(96);
noisePalette.setSpeed(SPEED);
noisePalette.setScale(SCALE);
}
void loop() {
noisePalette.setSpeed(SPEED);
noisePalette.setScale(SCALE);
EVERY_N_MILLISECONDS(5000) { noisePalette.changeToRandomPalette(); }
noisePalette.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}
#endif // End of the non-AVR code section

View File

@@ -0,0 +1,682 @@
/// @file FxNoiseRing.ino
/// @brief Noise effect on circular ring with ScreenMap
/// @example FxNoiseRing.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 <Arduino.h>
#include <FastLED.h>
#include "fl/json.h"
#include "fl/math_macros.h"
#include "fl/warn.h"
#include "noisegen.h"
#include "fl/screenmap.h"
#include "fl/slice.h"
#include "fl/ui.h"
#include "sensors/pir.h"
#include "./simple_timer.h"
#include "fl/sstream.h"
#include "fl/assert.h"
#define LED_PIN 2
#define COLOR_ORDER GRB // Color order matters for a real device, web-compiler will ignore this.
#define NUM_LEDS 250
#define PIN_PIR 0
#define PIR_LATCH_MS 60000 // how long to keep the PIR sensor active after a trigger
#define PIR_RISING_TIME 1000 // how long to fade in the PIR sensor
#define PIR_FALLING_TIME 1000 // how long to fade out the PIR sensor
using namespace fl;
CRGB leds[NUM_LEDS];
// Enhanced coordinate system for ring-based effects
struct RingCoord {
float angle; // Position on ring (0 to 2π)
float radius; // Distance from center (normalized 0-1)
float x, y; // Cartesian coordinates
int led_index; // LED position on strip
};
// Convert LED index to ring coordinates
RingCoord calculateRingCoord(int led_index, int num_leds, float time_offset = 0.0f) {
RingCoord coord;
coord.led_index = led_index;
coord.angle = (led_index * 2.0f * M_PI / num_leds) + time_offset;
coord.radius = 1.0f; // Fixed radius for ring
coord.x = cos(coord.angle);
coord.y = sin(coord.angle);
return coord;
}
// Performance optimization with lookup tables
class RingLUT {
private:
float cos_table[NUM_LEDS];
float sin_table[NUM_LEDS];
public:
void initialize() {
for(int i = 0; i < NUM_LEDS; i++) {
float angle = i * 2.0f * M_PI / NUM_LEDS;
cos_table[i] = cos(angle);
sin_table[i] = sin(angle);
}
}
RingCoord fastRingCoord(int led_index, float time_offset = 0.0f) {
RingCoord coord;
coord.led_index = led_index;
coord.angle = (led_index * 2.0f * M_PI / NUM_LEDS) + time_offset;
coord.x = cos_table[led_index];
coord.y = sin_table[led_index];
coord.radius = 1.0f;
return coord;
}
};
// Plasma wave parameters
struct PlasmaParams {
float time_scale = 1.0f;
float noise_intensity = 0.5f;
float noise_amplitude = 0.8f;
uint8_t time_bitshift = 5;
uint8_t hue_offset = 0;
float brightness = 1.0f;
};
// Plasma wave generator - Featured Implementation
class PlasmaWaveGenerator {
private:
struct WaveSource {
float x, y; // Source position
float frequency; // Wave frequency
float amplitude; // Wave strength
float phase_speed; // Phase evolution rate
};
WaveSource sources[4] = {
{0.5f, 0.5f, 1.0f, 1.0f, 0.8f}, // Center source
{0.0f, 0.0f, 1.5f, 0.8f, 1.2f}, // Corner source
{1.0f, 1.0f, 0.8f, 1.2f, 0.6f}, // Opposite corner
{0.5f, 0.0f, 1.2f, 0.9f, 1.0f} // Edge source
};
public:
CRGB calculatePlasmaPixel(const RingCoord& coord, uint32_t time_ms, const PlasmaParams& params) {
float time_scaled = time_ms * params.time_scale * 0.001f;
// Calculate wave interference
float wave_sum = 0.0f;
for (int i = 0; i < 4; i++) {
float dx = coord.x - sources[i].x;
float dy = coord.y - sources[i].y;
float distance = sqrt(dx*dx + dy*dy);
float wave_phase = distance * sources[i].frequency + time_scaled * sources[i].phase_speed;
wave_sum += sin(wave_phase) * sources[i].amplitude;
}
// Add noise modulation for organic feel
float noise_scale = params.noise_intensity;
float noise_x = coord.x * 0xffff * noise_scale;
float noise_y = coord.y * 0xffff * noise_scale;
uint32_t noise_time = time_ms << params.time_bitshift;
float noise_mod = (inoise16(noise_x, noise_y, noise_time) - 32768) / 65536.0f;
wave_sum += noise_mod * params.noise_amplitude;
// Map to color space
return mapWaveToColor(wave_sum, params);
}
private:
CRGB mapWaveToColor(float wave_value, const PlasmaParams& params) {
// Normalize wave to 0-1 range
float normalized = (wave_value + 4.0f) / 8.0f; // Assuming max amplitude ~4
normalized = constrain(normalized, 0.0f, 1.0f);
// Create flowing hue based on wave phase
uint8_t hue = (uint8_t)(normalized * 255.0f + params.hue_offset) % 256;
// Dynamic saturation based on wave intensity
float intensity = abs(wave_value);
uint8_t sat = (uint8_t)(192 + intensity * 63); // High saturation with variation
// Brightness modulation
uint8_t val = (uint8_t)(normalized * 255.0f * params.brightness);
return CHSV(hue, sat, val);
}
};
// Advanced Color Palette System
class ColorPaletteManager {
private:
uint8_t current_palette = 0;
uint32_t last_palette_change = 0;
static const uint32_t PALETTE_CHANGE_INTERVAL = 5000; // 5 seconds
public:
void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_palette) {
if (auto_cycle_enabled) {
if (now - last_palette_change > PALETTE_CHANGE_INTERVAL) {
current_palette = (current_palette + 1) % 5;
last_palette_change = now;
}
} else {
current_palette = manual_palette;
}
}
CRGB mapColor(float hue_norm, float intensity, float special_param = 0.0f) {
switch(current_palette) {
case 0: return mapSunsetBoulevard(hue_norm, intensity, special_param);
case 1: return mapOceanBreeze(hue_norm, intensity, special_param);
case 2: return mapNeonNights(hue_norm, intensity, special_param);
case 3: return mapForestWhisper(hue_norm, intensity, special_param);
case 4: return mapGalaxyExpress(hue_norm, intensity, special_param);
default: return mapSunsetBoulevard(hue_norm, intensity, special_param);
}
}
private:
CRGB mapSunsetBoulevard(float hue_norm, float intensity, float special_param) {
// Warm oranges, deep reds, golden yellows (Hue 0-45)
uint8_t hue = (uint8_t)(hue_norm * 45);
uint8_t sat = 200 + (uint8_t)(intensity * 55);
uint8_t val = 150 + (uint8_t)(intensity * 105);
return CHSV(hue, sat, val);
}
CRGB mapOceanBreeze(float hue_norm, float intensity, float special_param) {
// Deep blues, aqua, seafoam green (Hue 120-210)
uint8_t hue = 120 + (uint8_t)(hue_norm * 90);
uint8_t sat = 180 + (uint8_t)(intensity * 75);
uint8_t val = 120 + (uint8_t)(intensity * 135);
return CHSV(hue, sat, val);
}
CRGB mapNeonNights(float hue_norm, float intensity, float special_param) {
// Electric pink, cyan, purple, lime green - high contrast
uint8_t base_hues[] = {0, 85, 128, 192}; // Red, Cyan, Pink, Purple
uint8_t selected_hue = base_hues[(int)(hue_norm * 4) % 4];
uint8_t sat = 255; // Maximum saturation for neon effect
uint8_t val = 100 + (uint8_t)(intensity * 155);
return CHSV(selected_hue, sat, val);
}
CRGB mapForestWhisper(float hue_norm, float intensity, float special_param) {
// Deep greens, earth browns, golden highlights (Hue 60-150)
uint8_t hue = 60 + (uint8_t)(hue_norm * 90);
uint8_t sat = 150 + (uint8_t)(intensity * 105);
uint8_t val = 100 + (uint8_t)(intensity * 155);
return CHSV(hue, sat, val);
}
CRGB mapGalaxyExpress(float hue_norm, float intensity, float special_param) {
// Deep purples, cosmic blues, silver stars (Hue 200-300)
if (special_param > 0.8f) {
// Silver/white stars
uint8_t brightness = 200 + (uint8_t)(intensity * 55);
return CRGB(brightness, brightness, brightness);
} else {
uint8_t hue = 200 + (uint8_t)(hue_norm * 100);
uint8_t sat = 180 + (uint8_t)(intensity * 75);
uint8_t val = 80 + (uint8_t)(intensity * 175);
return CHSV(hue, sat, val);
}
}
};
// ALL 10 ALGORITHM IMPLEMENTATIONS
CRGB drawCosmicSwirl(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float time_factor = time_ms * 0.0008f;
// Multi-octave noise for organic complexity
float noise1 = inoise16(coord.x * 2000, coord.y * 2000, time_factor * 1000) / 65536.0f;
float noise2 = inoise16(coord.x * 1000, coord.y * 1000, time_factor * 2000) / 65536.0f * 0.5f;
float noise3 = inoise16(coord.x * 4000, coord.y * 4000, time_factor * 500) / 65536.0f * 0.25f;
float combined_noise = noise1 + noise2 + noise3;
float hue_norm = (combined_noise + coord.angle / (2*M_PI) + 1.0f) * 0.5f;
float intensity = (combined_noise + 1.0f) * 0.5f;
return palette.mapColor(hue_norm, intensity);
}
CRGB drawElectricStorm(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
uint32_t fast_time = time_ms << 3; // 8x time acceleration
float x_noise = coord.x * 8000;
float y_noise = coord.y * 8000;
uint16_t noise1 = inoise16(x_noise, y_noise, fast_time);
uint16_t noise2 = inoise16(x_noise + 10000, y_noise + 10000, fast_time + 5000);
uint8_t threshold = 200;
bool lightning = (noise1 >> 8) > threshold || (noise2 >> 8) > threshold;
if (lightning) {
float lightning_intensity = max((noise1 >> 8) - threshold, (noise2 >> 8) - threshold) / 55.0f;
return palette.mapColor(0.7f, lightning_intensity, 1.0f); // Special lightning effect
} else {
float storm_intensity = (noise1 >> 8) / 1020.0f; // Very low intensity for background
return palette.mapColor(0.6f, storm_intensity);
}
}
CRGB drawLavaLamp(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float slow_time = time_ms * 0.0002f;
float blob_scale = 800;
uint16_t primary_noise = inoise16(coord.x * blob_scale, coord.y * blob_scale, slow_time * 1000);
uint16_t secondary_noise = inoise16(coord.x * blob_scale * 0.5f, coord.y * blob_scale * 0.5f, slow_time * 1500);
float blob_value = (primary_noise + secondary_noise * 0.3f) / 65536.0f;
if (blob_value > 0.6f) {
// Hot blob center
float intensity = (blob_value - 0.6f) / 0.4f;
return palette.mapColor(0.1f, intensity); // Warm colors
} else if (blob_value > 0.3f) {
// Blob edge gradient
float edge_factor = (blob_value - 0.3f) / 0.3f;
return palette.mapColor(0.2f, edge_factor);
} else {
// Background
return palette.mapColor(0.8f, 0.2f); // Cool background
}
}
CRGB drawDigitalRain(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float vertical_pos = sin(coord.angle) * 0.5f + 0.5f;
float cascade_speed = 0.002f;
float time_offset = time_ms * cascade_speed;
int stream_id = (int)(coord.angle * 10) % 8;
float stream_phase = fmod(vertical_pos + time_offset + stream_id * 0.125f, 1.0f);
uint16_t noise = inoise16(stream_id * 1000, stream_phase * 10000, time_ms / 4);
uint8_t digital_value = (noise >> 8) > 128 ? 255 : 0;
if (digital_value > 0) {
float intensity = 1.0f - stream_phase * 0.8f; // Fade trailing
return palette.mapColor(0.4f, intensity); // Matrix green area
} else {
return CRGB::Black;
}
}
CRGB drawGlitchCity(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
uint32_t glitch_time = (time_ms / 100) * 100; // Quantize time
uint16_t noise1 = inoise16(coord.x * 3000, coord.y * 3000, glitch_time);
uint16_t noise2 = inoise16(coord.x * 5000, coord.y * 5000, glitch_time + 1000);
uint16_t glitch_value = noise1 ^ noise2; // XOR for harsh digital effects
if ((glitch_value & 0xF000) == 0xF000) {
return CRGB(255, 255, 255); // Full-bright glitch flash
}
float intensity = (glitch_value & 0xFF) / 255.0f;
float hue_chaos = ((glitch_value >> 8) & 0xFF) / 255.0f;
return palette.mapColor(hue_chaos, intensity, 0.5f);
}
CRGB drawOceanDepths(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float ocean_time = time_ms * 0.0005f;
float current1 = inoise16(coord.x * 1200, coord.y * 1200, ocean_time * 800) / 65536.0f;
float current2 = inoise16(coord.x * 2400, coord.y * 2400, ocean_time * 600) / 65536.0f * 0.5f;
float current3 = inoise16(coord.x * 600, coord.y * 600, ocean_time * 1000) / 65536.0f * 0.3f;
float depth_factor = (current1 + current2 + current3 + 1.5f) / 3.0f;
float hue_variation = (current2 + 0.5f);
return palette.mapColor(hue_variation, depth_factor);
}
CRGB drawFireDance(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float vertical_component = sin(coord.angle) * 0.5f + 0.5f;
float flame_x = coord.x * 1500;
float flame_y = coord.y * 1500 + time_ms * 0.003f;
uint16_t turbulence = inoise16(flame_x, flame_y, time_ms);
float flame_intensity = (turbulence / 65536.0f) * (1.0f - vertical_component * 0.3f);
float fire_hue = flame_intensity * 0.15f; // Red to orange range
return palette.mapColor(fire_hue, flame_intensity);
}
CRGB drawNebulaDrift(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float nebula_time = time_ms * 0.0003f;
float cloud1 = inoise16(coord.x * 800, coord.y * 800, nebula_time * 1000) / 65536.0f;
float cloud2 = inoise16(coord.x * 1600, coord.y * 1600, nebula_time * 700) / 65536.0f * 0.5f;
float cloud3 = inoise16(coord.x * 400, coord.y * 400, nebula_time * 1200) / 65536.0f * 0.25f;
float nebula_density = cloud1 + cloud2 + cloud3;
uint16_t star_noise = inoise16(coord.x * 4000, coord.y * 4000, nebula_time * 200);
bool is_star = (star_noise > 60000);
if (is_star) {
float star_intensity = (star_noise - 60000) / 5536.0f;
return palette.mapColor(0.0f, star_intensity, 1.0f); // Stars
} else {
float hue_drift = (nebula_density + 1.0f) * 0.5f;
float intensity = (nebula_density + 1.0f) * 0.4f;
return palette.mapColor(hue_drift, intensity);
}
}
CRGB drawBinaryPulse(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
float pulse_period = 2000.0f;
float pulse_phase = fmod(time_ms, pulse_period) / pulse_period;
float distance_from_center = sqrt(coord.x * coord.x + coord.y * coord.y);
float ring_frequency = 5.0f;
float pulse_offset = pulse_phase * 2.0f;
float ring_value = sin((distance_from_center * ring_frequency - pulse_offset) * 2 * M_PI);
uint16_t noise = inoise16(coord.x * 2000, coord.y * 2000, time_ms / 8);
float digital_mod = ((noise >> 8) > 128) ? 1.0f : -0.5f;
float final_value = ring_value * digital_mod;
if (final_value > 0.3f) {
return palette.mapColor(0.8f, final_value, 0.8f); // Active pulse
} else if (final_value > -0.2f) {
float transition_intensity = (final_value + 0.2f) * 2.0f;
return palette.mapColor(0.3f, transition_intensity); // Transition zones
} else {
return palette.mapColor(0.7f, 0.1f); // Background
}
}
// Enhanced variant manager with ALL 10 ALGORITHMS and smooth transitions
class NoiseVariantManager {
private:
uint8_t current_variant = 0;
uint8_t target_variant = 0;
float transition_progress = 1.0f; // 0.0 = old, 1.0 = new
uint32_t transition_start = 0;
static const uint32_t TRANSITION_DURATION = 1500; // 1.5 second fade for smoother transitions
// Algorithm instances
PlasmaWaveGenerator plasma_gen;
PlasmaParams plasma_params;
ColorPaletteManager& palette_manager;
public:
NoiseVariantManager(ColorPaletteManager& palette_mgr) : palette_manager(palette_mgr) {}
void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_variant, const PlasmaParams& params) {
plasma_params = params;
// Handle automatic cycling vs manual override
if (auto_cycle_enabled) {
EVERY_N_MILLISECONDS(12000) { // Slightly longer for each variant
startTransition((current_variant + 1) % 10, now); // ALL 10 variants
}
} else if (manual_variant != target_variant && transition_progress >= 1.0f) {
// Manual override
startTransition(manual_variant, now);
}
// Update transition progress
if (transition_progress < 1.0f) {
uint32_t elapsed = now - transition_start;
transition_progress = min(1.0f, elapsed / (float)TRANSITION_DURATION);
if (transition_progress >= 1.0f) {
current_variant = target_variant;
}
}
}
CRGB renderPixel(const RingCoord& coord, uint32_t time_ms) {
if (transition_progress >= 1.0f) {
// No transition, render current variant
return renderVariant(current_variant, coord, time_ms);
} else {
// Advanced cross-fade with brightness preservation
CRGB old_color = renderVariant(current_variant, coord, time_ms);
CRGB new_color = renderVariant(target_variant, coord, time_ms);
return smoothLerpCRGB(old_color, new_color, transition_progress);
}
}
uint8_t getCurrentVariant() const { return current_variant; }
const char* getCurrentVariantName() const {
const char* names[] = {
"Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves",
"Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse"
};
return names[current_variant % 10];
}
private:
void startTransition(uint8_t new_variant, uint32_t now) {
target_variant = new_variant % 10; // Ensure valid range
transition_start = now;
transition_progress = 0.0f;
}
CRGB renderVariant(uint8_t variant, const RingCoord& coord, uint32_t time_ms) {
switch(variant % 10) {
case 0: return drawCosmicSwirl(coord, time_ms, palette_manager);
case 1: return drawElectricStorm(coord, time_ms, palette_manager);
case 2: return drawLavaLamp(coord, time_ms, palette_manager);
case 3: return drawDigitalRain(coord, time_ms, palette_manager);
case 4: return drawPlasmaWithPalette(coord, time_ms, palette_manager);
case 5: return drawGlitchCity(coord, time_ms, palette_manager);
case 6: return drawOceanDepths(coord, time_ms, palette_manager);
case 7: return drawFireDance(coord, time_ms, palette_manager);
case 8: return drawNebulaDrift(coord, time_ms, palette_manager);
case 9: return drawBinaryPulse(coord, time_ms, palette_manager);
default: return drawCosmicSwirl(coord, time_ms, palette_manager);
}
}
// Enhanced Plasma Waves with palette integration
CRGB drawPlasmaWithPalette(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) {
// Generate base plasma waves
CRGB plasma_color = plasma_gen.calculatePlasmaPixel(coord, time_ms, plasma_params);
// Extract intensity and hue information from plasma
float intensity = (plasma_color.r + plasma_color.g + plasma_color.b) / 765.0f;
// Calculate wave interference for hue mapping
float time_scaled = time_ms * plasma_params.time_scale * 0.001f;
float wave_sum = 0.0f;
// Simplified wave calculation for hue determination
float dx = coord.x - 0.5f;
float dy = coord.y - 0.5f;
float distance = sqrt(dx*dx + dy*dy);
float wave_phase = distance * 2.0f + time_scaled * 1.5f;
wave_sum = sin(wave_phase);
float hue_norm = (wave_sum + 1.0f) * 0.5f; // Normalize to 0-1
// Use palette system for consistent color theming
return palette.mapColor(hue_norm, intensity, intensity > 0.8f ? 1.0f : 0.0f);
}
// Enhanced interpolation with brightness preservation and smooth curves
CRGB smoothLerpCRGB(const CRGB& a, const CRGB& b, float t) {
// Apply smooth curve to transition
float smooth_t = t * t * (3.0f - 2.0f * t); // Smoothstep function
// Preserve brightness during transition to avoid flickering
float brightness_a = (a.r + a.g + a.b) / 765.0f;
float brightness_b = (b.r + b.g + b.b) / 765.0f;
float target_brightness = brightness_a + (brightness_b - brightness_a) * smooth_t;
CRGB result = CRGB(
a.r + (int)((b.r - a.r) * smooth_t),
a.g + (int)((b.g - a.g) * smooth_t),
a.b + (int)((b.b - a.b) * smooth_t)
);
// Brightness compensation
float current_brightness = (result.r + result.g + result.b) / 765.0f;
if (current_brightness > 0.01f) {
float compensation = target_brightness / current_brightness;
compensation = min(compensation, 2.0f); // Limit boost
result.r = min(255, (int)(result.r * compensation));
result.g = min(255, (int)(result.g * compensation));
result.b = min(255, (int)(result.b * compensation));
}
return result;
}
};
// ALL 10 Variant names for UI
fl::string variant_names[10] = {
"Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves",
"Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse"
};
// 5 Color Palette names for UI
fl::string palette_names[5] = {
"Sunset Boulevard", "Ocean Breeze", "Neon Nights", "Forest Whisper", "Galaxy Express"
};
// Helper functions to get indices from names
uint8_t getVariantIndex(const fl::string& name) {
for (int i = 0; i < 10; i++) {
if (variant_names[i] == name) {
return i;
}
}
return 0; // Default to first variant
}
uint8_t getPaletteIndex(const fl::string& name) {
for (int i = 0; i < 5; i++) {
if (palette_names[i] == name) {
return i;
}
}
return 0; // Default to first palette
}
// Global instances - order matters for initialization
ColorPaletteManager palette_manager;
NoiseVariantManager variant_manager(palette_manager);
RingLUT ring_lut;
// KICKASS UI controls - comprehensive control suite
UISlider brightness("Brightness", 1, 0, 1);
UISlider scale("Scale", 4, .1, 4, .1);
UISlider timeBitshift("Time Bitshift", 5, 0, 16, 1);
UISlider timescale("Time Scale", 1, .1, 10, .1);
// Advanced variant and palette controls
UIDropdown variants("Noise Variants", variant_names);
UIDropdown palettes("Color Palettes", palette_names);
UICheckbox autoCycle("Auto Cycle Effects", true);
UICheckbox autoPalette("Auto Cycle Palettes", true);
// This PIR type is special because it will bind to a pin for a real device,
// but also provides a UIButton when run in the simulator.
Pir pir(PIN_PIR, PIR_LATCH_MS, PIR_RISING_TIME, PIR_FALLING_TIME);
UICheckbox useDither("Use Binary Dither", true);
Timer timer;
float current_brightness = 0;
// Save a pointer to the controller so that we can modify the dither in real time.
CLEDController* controller = nullptr;
void setup() {
Serial.begin(115200);
// ScreenMap is purely something that is needed for the sketch to correctly
// show on the web display. For deployements to real devices, this essentially
// becomes a no-op.
ScreenMap xyMap = ScreenMap::Circle(NUM_LEDS, 2.0, 2.0);
controller = &FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setDither(DISABLE_DITHER)
.setScreenMap(xyMap);
FastLED.setBrightness(brightness);
pir.activate(millis()); // Activate the PIR sensor on startup.
// Initialize performance optimizations
ring_lut.initialize();
}
void draw(uint32_t now) {
// Configure plasma parameters from UI controls with enhanced scaling
PlasmaParams plasma_params;
plasma_params.time_scale = timescale.as<float>();
plasma_params.noise_intensity = scale.as<float>() * 0.8f; // Slightly reduce for better visual balance
plasma_params.brightness = brightness.as<float>();
plasma_params.time_bitshift = timeBitshift.as<int>();
plasma_params.hue_offset = (now / 100) % 256; // Slow hue rotation for extra dynamism
plasma_params.noise_amplitude = 0.6f + 0.4f * sin(now * 0.001f); // Breathing noise effect
// Update palette manager with auto-cycling and manual control
palette_manager.update(now, autoPalette.value(), getPaletteIndex(palettes.value()));
// Update variant manager with enhanced parameters
variant_manager.update(now, autoCycle.value(), getVariantIndex(variants.value()), plasma_params);
// KICKASS rendering with performance optimizations
for (int i = 0; i < NUM_LEDS; i++) {
RingCoord coord = ring_lut.fastRingCoord(i);
CRGB pixel_color = variant_manager.renderPixel(coord, now);
// Apply global brightness and gamma correction for better visual quality
float global_brightness = brightness.as<float>();
pixel_color.r = (uint8_t)(pixel_color.r * global_brightness);
pixel_color.g = (uint8_t)(pixel_color.g * global_brightness);
pixel_color.b = (uint8_t)(pixel_color.b * global_brightness);
leds[i] = pixel_color;
}
// Optional: Add subtle sparkle overlay for extra visual interest
EVERY_N_MILLISECONDS(50) {
// Add random sparkles to 1% of LEDs
int sparkle_count = NUM_LEDS / 100 + 1;
for (int s = 0; s < sparkle_count; s++) {
int sparkle_pos = random16() % NUM_LEDS;
if (random8() > 250) { // Very rare sparkles
leds[sparkle_pos] = blend(leds[sparkle_pos], CRGB::White, 128);
}
}
}
}
void loop() {
// Allow the dither to be enabled and disabled.
controller->setDither(useDither ? BINARY_DITHER : DISABLE_DITHER);
uint32_t now = millis();
uint8_t bri = pir.transition(now);
FastLED.setBrightness(bri * brightness.as<float>());
// Apply leds generation to the leds.
draw(now);
FastLED.show();
}

View File

@@ -0,0 +1,11 @@
#include "fl/sketch_macros.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "FxNoiseRing.h"
#else
void setup() {}
void loop() {}
#endif // SKETCH_HAS_LOTS_OF_MEMORY

View File

@@ -0,0 +1,760 @@
# NoiseRing Enhanced Design Document
## Overview
Enhanced version of the FxNoiseRing example that automatically cycles through different noise effects and color palettes, providing dynamic visual variety with user controls for manual selection.
**Featured Implementation**: **Plasma Waves** - Advanced graphics technique showcasing sine wave interference with noise modulation, demonstrating sophisticated mathematical visualization on circular LED arrays.
## Core Features
### Automatic Cycling
- **Palette Rotation**: Every 5 seconds using `EVERY_N_MILLISECONDS(5000)`
- **Noise Effect Rotation**: Every 10 seconds using `EVERY_N_MILLISECONDS(10000)`
- **User Override**: Dropdown controls allow manual selection to override automatic cycling
### User Interface Controls
- **Variants Dropdown**: "Noise Variants" - Manual selection of noise effects (0-9)
- **Palettes Dropdown**: "Color Palettes" - Manual selection of color schemes (0-4)
- **Auto Cycle Checkbox**: "Auto Cycle" - Enable/disable automatic rotation
- **Existing Controls**: Retain all current sliders (Brightness, Scale, Time Bitshift, Time Scale, PIR, Dither)
## 10 Noise Variations - Detailed Algorithmic Implementation
### 1. "Cosmic Swirl" - Enhanced Perlin Flow
- **Description**: Classic perlin noise with slow, flowing movements using multi-octave complexity
- **Parameters**: Base noise with moderate scale, gentle time progression
- **Characteristics**: Smooth gradients, organic flow patterns
**Algorithm**:
```cpp
CRGB drawCosmicSwirl(const RingCoord& coord, uint32_t time_ms) {
float time_factor = time_ms * 0.0008f;
// Multi-octave noise for organic complexity
float noise1 = inoise16(coord.x * 2000, coord.y * 2000, time_factor * 1000) / 65536.0f;
float noise2 = inoise16(coord.x * 1000, coord.y * 1000, time_factor * 2000) / 65536.0f * 0.5f;
float noise3 = inoise16(coord.x * 4000, coord.y * 4000, time_factor * 500) / 65536.0f * 0.25f;
float combined_noise = noise1 + noise2 + noise3;
// Flowing hue with gentle progression
uint8_t hue = (uint8_t)((combined_noise + coord.angle / (2*M_PI)) * 255) % 256;
uint8_t sat = 220 + (uint8_t)(abs(noise2) * 35);
uint8_t val = 180 + (uint8_t)(combined_noise * 75);
return CHSV(hue, sat, val);
}
```
### 2. "Electric Storm" - High-Frequency Chaos
- **Description**: High-frequency noise with rapid temporal changes creating lightning effects
- **Parameters**: 8x time acceleration, high spatial frequency, quantized thresholds
- **Characteristics**: Crackling, energetic, lightning-like patterns
**Algorithm**:
```cpp
CRGB drawElectricStorm(const RingCoord& coord, uint32_t time_ms) {
// Rapid temporal changes with quantized effects
uint32_t fast_time = time_ms << 3; // 8x time acceleration
// High-frequency spatial noise
float x_noise = coord.x * 8000;
float y_noise = coord.y * 8000;
uint16_t noise1 = inoise16(x_noise, y_noise, fast_time);
uint16_t noise2 = inoise16(x_noise + 10000, y_noise + 10000, fast_time + 5000);
// Create lightning-like quantization
uint8_t threshold = 200;
bool lightning = (noise1 >> 8) > threshold || (noise2 >> 8) > threshold;
if (lightning) {
// Bright electric flash
uint8_t intensity = max((noise1 >> 8) - threshold, (noise2 >> 8) - threshold) * 4;
return CRGB(intensity, intensity, 255); // Electric blue-white
} else {
// Dark storm background
uint8_t hue = 160 + ((noise1 >> 10) % 32); // Blue-purple range
return CHSV(hue, 255, (noise1 >> 8) / 4); // Low brightness
}
}
```
### 3. "Lava Lamp" - Slow Blobby Movement
- **Description**: Slow, blobby movements with high contrast using low-frequency modulation
- **Parameters**: Ultra-low frequency, high amplitude, threshold-based blob creation
- **Characteristics**: Large, slow-moving color blobs with organic boundaries
**Algorithm**:
```cpp
CRGB drawLavaLamp(const RingCoord& coord, uint32_t time_ms) {
float slow_time = time_ms * 0.0002f; // Very slow movement
// Large-scale blob generation
float blob_scale = 800; // Large spatial scale for big blobs
uint16_t primary_noise = inoise16(coord.x * blob_scale, coord.y * blob_scale, slow_time * 1000);
uint16_t secondary_noise = inoise16(coord.x * blob_scale * 0.5f, coord.y * blob_scale * 0.5f, slow_time * 1500);
// Create blob boundaries with thresholding
float blob_value = (primary_noise + secondary_noise * 0.3f) / 65536.0f;
// High contrast blob regions
if (blob_value > 0.6f) {
// Hot blob center
uint8_t hue = 0 + (uint8_t)((blob_value - 0.6f) * 400); // Red to orange
return CHSV(hue, 255, 255);
} else if (blob_value > 0.3f) {
// Blob edge gradient
float edge_factor = (blob_value - 0.3f) / 0.3f;
uint8_t brightness = (uint8_t)(edge_factor * 255);
return CHSV(20, 200, brightness); // Orange edge
} else {
// Background
return CHSV(240, 100, 30); // Dark blue background
}
}
```
### 4. "Digital Rain" - Matrix Cascade
- **Description**: Matrix-style cascading effect using vertical noise mapping
- **Parameters**: Angle-to-vertical conversion, time-based cascade, stream segregation
- **Characteristics**: Vertical streams, binary-like transitions, matrix green
**Algorithm**:
```cpp
CRGB drawDigitalRain(const RingCoord& coord, uint32_t time_ms) {
// Convert angle to vertical position for cascade effect
float vertical_pos = sin(coord.angle) * 0.5f + 0.5f; // 0-1 range
// Time-based cascade with varying speeds
float cascade_speed = 0.002f;
float time_offset = time_ms * cascade_speed;
// Create vertical streams
int stream_id = (int)(coord.angle * 10) % 8; // 8 distinct streams
float stream_phase = fmod(vertical_pos + time_offset + stream_id * 0.125f, 1.0f);
// Binary-like transitions
uint16_t noise = inoise16(stream_id * 1000, stream_phase * 10000, time_ms / 4);
uint8_t digital_value = (noise >> 8) > 128 ? 255 : 0;
// Matrix green with digital artifacts
uint8_t green_intensity = digital_value;
uint8_t trailing = max(0, green_intensity - (int)(stream_phase * 200));
return CRGB(0, green_intensity, trailing / 2);
}
```
### 5. "Plasma Waves" - **FEATURED IMPLEMENTATION**
- **Description**: Multiple overlapping sine waves with noise modulation creating electromagnetic plasma effects
- **Parameters**: 4-source wave interference, noise modulation, dynamic color mapping
- **Characteristics**: Smooth wave interference patterns, flowing electromagnetic appearance
**Algorithm**:
```cpp
class PlasmaWaveGenerator {
private:
struct WaveSource {
float x, y; // Source position
float frequency; // Wave frequency
float amplitude; // Wave strength
float phase_speed; // Phase evolution rate
};
WaveSource sources[4] = {
{0.5f, 0.5f, 1.0f, 1.0f, 0.8f}, // Center source
{0.0f, 0.0f, 1.5f, 0.8f, 1.2f}, // Corner source
{1.0f, 1.0f, 0.8f, 1.2f, 0.6f}, // Opposite corner
{0.5f, 0.0f, 1.2f, 0.9f, 1.0f} // Edge source
};
public:
CRGB calculatePlasmaPixel(const RingCoord& coord, uint32_t time_ms, const PlasmaParams& params) {
float time_scaled = time_ms * params.time_scale * 0.001f;
// Calculate wave interference
float wave_sum = 0.0f;
for (int i = 0; i < 4; i++) {
float dx = coord.x - sources[i].x;
float dy = coord.y - sources[i].y;
float distance = sqrt(dx*dx + dy*dy);
float wave_phase = distance * sources[i].frequency + time_scaled * sources[i].phase_speed;
wave_sum += sin(wave_phase) * sources[i].amplitude;
}
// Add noise modulation for organic feel
float noise_scale = params.noise_intensity;
float noise_x = coord.x * 0xffff * noise_scale;
float noise_y = coord.y * 0xffff * noise_scale;
uint32_t noise_time = time_ms << params.time_bitshift;
float noise_mod = (inoise16(noise_x, noise_y, noise_time) - 32768) / 65536.0f;
wave_sum += noise_mod * params.noise_amplitude;
// Map to color space
return mapWaveToColor(wave_sum, params);
}
private:
CRGB mapWaveToColor(float wave_value, const PlasmaParams& params) {
// Normalize wave to 0-1 range
float normalized = (wave_value + 4.0f) / 8.0f; // Assuming max amplitude ~4
normalized = constrain(normalized, 0.0f, 1.0f);
// Create flowing hue based on wave phase
uint8_t hue = (uint8_t)(normalized * 255.0f + params.hue_offset) % 256;
// Dynamic saturation based on wave intensity
float intensity = abs(wave_value);
uint8_t sat = (uint8_t)(192 + intensity * 63); // High saturation with variation
// Brightness modulation
uint8_t val = (uint8_t)(normalized * 255.0f * params.brightness);
return CHSV(hue, sat, val);
}
};
struct PlasmaParams {
float time_scale = 1.0f;
float noise_intensity = 0.5f;
float noise_amplitude = 0.8f;
uint8_t time_bitshift = 5;
uint8_t hue_offset = 0;
float brightness = 1.0f;
};
```
### 6. "Glitch City" - Chaotic Digital Artifacts
- **Description**: Chaotic, stuttering effects with quantized noise and bit manipulation
- **Parameters**: Time quantization, XOR operations, random bit shifts
- **Characteristics**: Harsh transitions, digital artifacts, strobe-like effects
**Algorithm**:
```cpp
CRGB drawGlitchCity(const RingCoord& coord, uint32_t time_ms) {
// Stuttering time progression
uint32_t glitch_time = (time_ms / 100) * 100; // Quantize time to create stutters
// Bit manipulation for digital artifacts
uint16_t noise1 = inoise16(coord.x * 3000, coord.y * 3000, glitch_time);
uint16_t noise2 = inoise16(coord.x * 5000, coord.y * 5000, glitch_time + 1000);
// XOR operation for harsh digital effects
uint16_t glitch_value = noise1 ^ noise2;
// Random bit shifts for channel corruption
uint8_t r = (glitch_value >> (time_ms % 8)) & 0xFF;
uint8_t g = (glitch_value << (time_ms % 5)) & 0xFF;
uint8_t b = ((noise1 | noise2) >> 4) & 0xFF;
// Occasional full-bright flashes
if ((glitch_value & 0xF000) == 0xF000) {
return CRGB(255, 255, 255);
}
return CRGB(r, g, b);
}
```
### 7. "Ocean Depths" - Underwater Currents
- **Description**: Slow, deep undulations mimicking underwater currents with blue-green bias
- **Parameters**: Ultra-low frequency, blue-green color bias, depth-based brightness
- **Characteristics**: Calm, flowing, deep water feel with gentle undulations
**Algorithm**:
```cpp
CRGB drawOceanDepths(const RingCoord& coord, uint32_t time_ms) {
float ocean_time = time_ms * 0.0005f; // Very slow like deep water
// Multi-layer current simulation
float current1 = inoise16(coord.x * 1200, coord.y * 1200, ocean_time * 800) / 65536.0f;
float current2 = inoise16(coord.x * 2400, coord.y * 2400, ocean_time * 600) / 65536.0f * 0.5f;
float current3 = inoise16(coord.x * 600, coord.y * 600, ocean_time * 1000) / 65536.0f * 0.3f;
float depth_factor = current1 + current2 + current3;
// Ocean color palette (blue-green spectrum)
uint8_t base_hue = 140; // Cyan-blue
uint8_t hue_variation = (uint8_t)(abs(depth_factor) * 40); // Vary within blue-green
uint8_t final_hue = (base_hue + hue_variation) % 256;
// Depth-based brightness (deeper = darker)
uint8_t depth_brightness = 120 + (uint8_t)(depth_factor * 135);
uint8_t saturation = 200 + (uint8_t)(abs(current2) * 55);
return CHSV(final_hue, saturation, depth_brightness);
}
```
### 8. "Fire Dance" - Upward Flame Simulation
- **Description**: Flickering, flame-like patterns with upward bias and turbulent noise
- **Parameters**: Vertical gradient bias, turbulent noise, fire color palette
- **Characteristics**: Orange/red dominated, upward movement, flickering
**Algorithm**:
```cpp
CRGB drawFireDance(const RingCoord& coord, uint32_t time_ms) {
// Vertical bias for upward flame movement
float vertical_component = sin(coord.angle) * 0.5f + 0.5f; // 0 at bottom, 1 at top
// Turbulent noise with upward bias
float flame_x = coord.x * 1500;
float flame_y = coord.y * 1500 + time_ms * 0.003f; // Upward drift
uint16_t turbulence = inoise16(flame_x, flame_y, time_ms);
float flame_intensity = (turbulence / 65536.0f) * (1.0f - vertical_component * 0.3f);
// Fire color palette (red->orange->yellow)
uint8_t base_hue = 0; // Red
uint8_t hue_variation = (uint8_t)(flame_intensity * 45); // Up to orange/yellow
uint8_t final_hue = (base_hue + hue_variation) % 256;
uint8_t saturation = 255 - (uint8_t)(vertical_component * 100); // Less saturated at top
uint8_t brightness = (uint8_t)(flame_intensity * 255);
return CHSV(final_hue, saturation, brightness);
}
```
### 9. "Nebula Drift" - Cosmic Cloud Simulation
- **Description**: Slow cosmic clouds with starfield sparkles using multi-octave noise
- **Parameters**: Multiple noise octaves, sparse bright spots, cosmic color palette
- **Characteristics**: Misty backgrounds with occasional bright stars
**Algorithm**:
```cpp
CRGB drawNebulaDrift(const RingCoord& coord, uint32_t time_ms) {
float nebula_time = time_ms * 0.0003f; // Cosmic slow drift
// Multi-octave nebula clouds
float cloud1 = inoise16(coord.x * 800, coord.y * 800, nebula_time * 1000) / 65536.0f;
float cloud2 = inoise16(coord.x * 1600, coord.y * 1600, nebula_time * 700) / 65536.0f * 0.5f;
float cloud3 = inoise16(coord.x * 400, coord.y * 400, nebula_time * 1200) / 65536.0f * 0.25f;
float nebula_density = cloud1 + cloud2 + cloud3;
// Sparse starfield generation
uint16_t star_noise = inoise16(coord.x * 4000, coord.y * 4000, nebula_time * 200);
bool is_star = (star_noise > 60000); // Very sparse stars
if (is_star) {
// Bright white/blue stars
uint8_t star_brightness = 200 + ((star_noise - 60000) / 256);
return CRGB(star_brightness, star_brightness, 255);
} else {
// Nebula background
uint8_t nebula_hue = 200 + (uint8_t)(nebula_density * 80); // Purple-pink spectrum
uint8_t nebula_sat = 150 + (uint8_t)(abs(cloud2) * 105);
uint8_t nebula_bright = 40 + (uint8_t)(nebula_density * 120);
return CHSV(nebula_hue, nebula_sat, nebula_bright);
}
}
```
### 10. "Binary Pulse" - Digital Heartbeat
- **Description**: Digital heartbeat with expanding/contracting rings using threshold-based noise
- **Parameters**: Concentric pattern generation, rhythmic pulsing, geometric thresholds
- **Characteristics**: Rhythmic, geometric, tech-inspired
**Algorithm**:
```cpp
CRGB drawBinaryPulse(const RingCoord& coord, uint32_t time_ms) {
// Create rhythmic heartbeat timing
float pulse_period = 2000.0f; // 2-second pulse cycle
float pulse_phase = fmod(time_ms, pulse_period) / pulse_period; // 0-1 cycle
// Generate expanding rings from center
float distance_from_center = sqrt(coord.x * coord.x + coord.y * coord.y);
// Pulse wave propagation
float ring_frequency = 5.0f; // Number of rings
float pulse_offset = pulse_phase * 2.0f; // Expanding wave
float ring_value = sin((distance_from_center * ring_frequency - pulse_offset) * 2 * M_PI);
// Digital quantization
uint16_t noise = inoise16(coord.x * 2000, coord.y * 2000, time_ms / 8);
float digital_mod = ((noise >> 8) > 128) ? 1.0f : -0.5f;
float final_value = ring_value * digital_mod;
// Binary color mapping
if (final_value > 0.3f) {
// Active pulse regions
uint8_t intensity = (uint8_t)(final_value * 255);
return CRGB(intensity, 0, intensity); // Magenta pulse
} else if (final_value > -0.2f) {
// Transition zones
uint8_t dim_intensity = (uint8_t)((final_value + 0.2f) * 500);
return CRGB(0, dim_intensity, 0); // Green transitions
} else {
// Background
return CRGB(10, 0, 20); // Dark purple background
}
}
```
## 5 Color Palettes
### 1. "Sunset Boulevard"
- **Colors**: Warm oranges, deep reds, golden yellows
- **Description**: Classic sunset gradient perfect for relaxing ambiance
- **HSV Range**: Hue 0-45, high saturation, varying brightness
### 2. "Ocean Breeze"
- **Colors**: Deep blues, aqua, seafoam green, white caps
- **Description**: Cool ocean palette for refreshing visual effects
- **HSV Range**: Hue 120-210, medium-high saturation
### 3. "Neon Nights"
- **Colors**: Electric pink, cyan, purple, lime green
- **Description**: Cyberpunk-inspired high-contrast palette
- **HSV Range**: Saturated primaries, high brightness contrasts
### 4. "Forest Whisper"
- **Colors**: Deep greens, earth browns, golden highlights
- **Description**: Natural woodland palette for organic feels
- **HSV Range**: Hue 60-150, natural saturation levels
### 5. "Galaxy Express"
- **Colors**: Deep purples, cosmic blues, silver stars, pink nebula
- **Description**: Space-themed palette for cosmic adventures
- **HSV Range**: Hue 200-300, with bright white accents
## Implementation Strategy
### Core Mathematical Framework
```cpp
// Enhanced coordinate system for ring-based effects
struct RingCoord {
float angle; // Position on ring (0 to 2π)
float radius; // Distance from center (normalized 0-1)
float x, y; // Cartesian coordinates
int led_index; // LED position on strip
};
// Convert LED index to ring coordinates
RingCoord calculateRingCoord(int led_index, int num_leds, float time_offset = 0.0f) {
RingCoord coord;
coord.led_index = led_index;
coord.angle = (led_index * 2.0f * M_PI / num_leds) + time_offset;
coord.radius = 1.0f; // Fixed radius for ring
coord.x = cos(coord.angle);
coord.y = sin(coord.angle);
return coord;
}
// Performance optimization with lookup tables
class RingLUT {
private:
float cos_table[NUM_LEDS];
float sin_table[NUM_LEDS];
public:
void initialize() {
for(int i = 0; i < NUM_LEDS; i++) {
float angle = i * 2.0f * M_PI / NUM_LEDS;
cos_table[i] = cos(angle);
sin_table[i] = sin(angle);
}
}
RingCoord fastRingCoord(int led_index, float time_offset = 0.0f) {
RingCoord coord;
coord.led_index = led_index;
coord.angle = (led_index * 2.0f * M_PI / NUM_LEDS) + time_offset;
coord.x = cos_table[led_index];
coord.y = sin_table[led_index];
coord.radius = 1.0f;
return coord;
}
};
```
### Enhanced Control System with Smooth Transitions
```cpp
class NoiseVariantManager {
private:
uint8_t current_variant = 0;
uint8_t target_variant = 0;
float transition_progress = 1.0f; // 0.0 = old, 1.0 = new
uint32_t transition_start = 0;
static const uint32_t TRANSITION_DURATION = 1000; // 1 second fade
// Algorithm instances
PlasmaWaveGenerator plasma_gen;
PlasmaParams plasma_params;
public:
void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_variant) {
// Handle automatic cycling vs manual override
if (auto_cycle_enabled) {
EVERY_N_MILLISECONDS(10000) {
startTransition((current_variant + 1) % 10, now);
}
} else if (manual_variant != target_variant && transition_progress >= 1.0f) {
// Manual override
startTransition(manual_variant, now);
}
// Update transition progress
if (transition_progress < 1.0f) {
uint32_t elapsed = now - transition_start;
transition_progress = min(1.0f, elapsed / (float)TRANSITION_DURATION);
if (transition_progress >= 1.0f) {
current_variant = target_variant;
}
}
}
CRGB renderPixel(const RingCoord& coord, uint32_t time_ms) {
if (transition_progress >= 1.0f) {
// No transition, render current variant
return renderVariant(current_variant, coord, time_ms);
} else {
// Blend between variants
CRGB old_color = renderVariant(current_variant, coord, time_ms);
CRGB new_color = renderVariant(target_variant, coord, time_ms);
return lerpCRGB(old_color, new_color, transition_progress);
}
}
private:
void startTransition(uint8_t new_variant, uint32_t now) {
target_variant = new_variant;
transition_start = now;
transition_progress = 0.0f;
}
CRGB renderVariant(uint8_t variant, const RingCoord& coord, uint32_t time_ms) {
switch(variant) {
case 0: return drawCosmicSwirl(coord, time_ms);
case 1: return drawElectricStorm(coord, time_ms);
case 2: return drawLavaLamp(coord, time_ms);
case 3: return drawDigitalRain(coord, time_ms);
case 4: return plasma_gen.calculatePlasmaPixel(coord, time_ms, plasma_params);
case 5: return drawGlitchCity(coord, time_ms);
case 6: return drawOceanDepths(coord, time_ms);
case 7: return drawFireDance(coord, time_ms);
case 8: return drawNebulaDrift(coord, time_ms);
case 9: return drawBinaryPulse(coord, time_ms);
default: return CRGB::Black;
}
}
CRGB lerpCRGB(const CRGB& a, const CRGB& b, float t) {
return CRGB(
a.r + (int)((b.r - a.r) * t),
a.g + (int)((b.g - a.g) * t),
a.b + (int)((b.b - a.b) * t)
);
}
};
```
### Data Structures and UI Integration
```cpp
// Enhanced data structures
String variant_names[10] = {
"Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves",
"Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse"
};
String palette_names[5] = {
"Sunset Boulevard", "Ocean Breeze", "Neon Nights", "Forest Whisper", "Galaxy Express"
};
// Global instances
NoiseVariantManager variant_manager;
RingLUT ring_lut;
// Enhanced UI controls
UIDropdown variants("Noise Variants", variant_names, 10);
UIDropdown palettes("Color Palettes", palette_names, 5);
UICheckbox autoCycle("Auto Cycle", true);
```
### Integration with Existing Framework
```cpp
void setup() {
Serial.begin(115200);
ScreenMap xyMap = ScreenMap::Circle(NUM_LEDS, 2.0, 2.0);
controller = &FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setDither(DISABLE_DITHER)
.setScreenMap(xyMap);
FastLED.setBrightness(brightness);
pir.activate(millis());
// Initialize performance optimizations
ring_lut.initialize();
}
void draw(uint32_t now) {
// Update variant manager
variant_manager.update(now, autoCycle.value(), variants.value());
// Render each LED with current variant
for (int i = 0; i < NUM_LEDS; i++) {
RingCoord coord = ring_lut.fastRingCoord(i);
leds[i] = variant_manager.renderPixel(coord, now);
}
}
void loop() {
controller->setDither(useDither ? BINARY_DITHER : DISABLE_DITHER);
uint32_t now = millis();
uint8_t bri = pir.transition(now);
FastLED.setBrightness(bri * brightness.as<float>());
draw(now);
FastLED.show();
}
```
## Technical Considerations
### Performance Optimization
- **Lookup Table Pre-computation**: Pre-calculate trigonometric values for ring positions
- **Fixed-Point Arithmetic**: Use integer math where possible for embedded systems
- **Noise Caching**: Cache noise parameters between frames for consistent animation
- **Memory-Efficient Algorithms**: Optimize noise calculations for real-time performance
- **Parallel Processing**: Structure algorithms for potential multi-core optimization
### Memory Management
- **PROGMEM Storage**: Store palettes and static data in program memory for Arduino compatibility
- **Dynamic Allocation Avoidance**: Minimize heap usage during effect transitions
- **Stack Optimization**: Use local variables efficiently in nested algorithm calls
- **Buffer Management**: Reuse coordinate calculation buffers where possible
### Mathematical Precision
- **16-bit Noise Space**: Maintain precision in noise calculations before 8-bit mapping
- **Floating Point Efficiency**: Balance precision vs. performance based on target platform
- **Color Space Optimization**: Use HSV for smooth transitions, RGB for final output
- **Numerical Stability**: Prevent overflow/underflow in wave interference calculations
### User Experience
- **Smooth Transitions**: 1-second cross-fade between effects using linear interpolation
- **Responsive Controls**: Immediate override of automatic cycling via manual selection
- **Visual Feedback**: Clear indication of current variant and palette selection
- **Performance Consistency**: Maintain stable frame rate across all effect variants
## First Pass Implementation: Plasma Waves
### Why Start with Plasma Waves?
1. **Visual Impact**: Most impressive demonstration of advanced graphics programming
2. **Mathematical Showcase**: Demonstrates sine wave interference and noise modulation
3. **Building Foundation**: Establishes the RingCoord system used by all other variants
4. **Performance Baseline**: Tests the most computationally intensive algorithm first
### Development Strategy
```cpp
// Phase 1: Core Infrastructure
void setupPlasmaDemo() {
// Initialize basic ring coordinate system
ring_lut.initialize();
// Configure plasma parameters
plasma_params.time_scale = timescale.as<float>();
plasma_params.noise_intensity = scale.as<float>();
plasma_params.brightness = brightness.as<float>();
}
// Phase 2: Plasma-Only Implementation
void drawPlasmaOnly(uint32_t now) {
for (int i = 0; i < NUM_LEDS; i++) {
RingCoord coord = ring_lut.fastRingCoord(i);
leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params);
}
}
// Phase 3: Add Manual Variants (No Auto-Cycling Yet)
void drawWithManualSelection(uint32_t now) {
uint8_t selected_variant = variants.value();
for (int i = 0; i < NUM_LEDS; i++) {
RingCoord coord = ring_lut.fastRingCoord(i);
switch(selected_variant) {
case 0: leds[i] = drawCosmicSwirl(coord, now); break;
case 1: leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params); break;
// Add variants incrementally
default: leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params);
}
}
}
// Phase 4: Full System with Auto-Cycling and Transitions
void drawFullSystem(uint32_t now) {
variant_manager.update(now, autoCycle.value(), variants.value());
for (int i = 0; i < NUM_LEDS; i++) {
RingCoord coord = ring_lut.fastRingCoord(i);
leds[i] = variant_manager.renderPixel(coord, now);
}
}
```
### Testing and Validation
1. **Plasma Waves Only**: Verify smooth wave interference and noise modulation
2. **Parameter Responsiveness**: Test all UI sliders affect plasma generation correctly
3. **Performance Metrics**: Measure frame rate with plasma algorithm on target hardware
4. **Visual Quality**: Confirm smooth color transitions and no artifacts
5. **Memory Usage**: Monitor RAM consumption during plasma calculations
### Incremental Development Plan
1. **Week 1**: Implement Plasma Waves algorithm and RingCoord system
2. **Week 2**: Add 2-3 simpler variants (Cosmic Swirl, Electric Storm, Fire Dance)
3. **Week 3**: Implement transition system and automatic cycling
4. **Week 4**: Add remaining variants and color palette system
5. **Week 5**: Optimization, polish, and platform-specific tuning
## Advanced Graphics Techniques Demonstrated
### Wave Interference Mathematics
The plasma algorithm showcases classical physics simulation:
- **Superposition Principle**: Multiple wave sources combine linearly
- **Phase Relationships**: Time-varying phase creates animation
- **Distance-Based Attenuation**: Realistic wave propagation modeling
- **Noise Modulation**: Organic variation through Perlin noise overlay
### Color Theory Implementation
- **HSV Color Space**: Smooth hue transitions for natural color flow
- **Saturation Modulation**: Dynamic saturation based on wave intensity
- **Brightness Mapping**: Normalized wave values to brightness curves
- **Gamma Correction**: Perceptually linear brightness progression
### Performance Optimization Strategies
- **Trigonometric Lookup**: Pre-computed sine/cosine tables
- **Fixed-Point Math**: Integer approximations for embedded platforms
- **Loop Unrolling**: Minimize function call overhead in tight loops
- **Memory Access Patterns**: Cache-friendly coordinate calculations
## Future Enhancements
### Advanced Features
- **Save/Load Configurations**: User-defined effect combinations and parameters
- **BPM Synchronization**: Music-reactive timing for effect transitions
- **Custom Palette Editor**: User-defined color schemes with preview
- **Effect Intensity Controls**: Per-variant amplitude and speed modulation
- **Multi-Ring Support**: Expand to multiple concentric LED rings
### Platform Extensions
- **Multi-Core Optimization**: Parallel processing for complex calculations
- **GPU Acceleration**: WebGL compute shaders for web platform
- **Hardware Acceleration**: Platform-specific optimizations (ESP32, Teensy)
- **Memory Mapping**: Direct hardware buffer access for maximum performance
### Algorithm Enhancements
- **Physically-Based Rendering**: More realistic light simulation
- **Particle Systems**: Dynamic particle-based effects
- **Fractal Algorithms**: Mandelbrot and Julia set visualizations
- **Audio Visualization**: Spectrum analysis and reactive algorithms

View File

@@ -0,0 +1,58 @@
#pragma once
#include "fl/stdint.h"
/**
* @brief A simple timer utility class for tracking timed events
*
* This class provides basic timer functionality for animations and effects.
* It can be used to track whether a specific duration has elapsed since
* the timer was started.
*/
class Timer {
public:
/**
* @brief Construct a new Timer object
*
* Creates a timer in the stopped state with zero duration.
*/
Timer() : start_time(0), duration(0), running(false) {}
/**
* @brief Start the timer with a specific duration
*
* @param now Current time in milliseconds (typically from millis())
* @param duration How long the timer should run in milliseconds
*/
void start(uint32_t now, uint32_t duration) {
start_time = now;
this->duration = duration;
running = true;
}
/**
* @brief Update the timer state based on current time
*
* Checks if the timer is still running based on the current time.
* If the specified duration has elapsed, the timer will stop.
*
* @param now Current time in milliseconds (typically from millis())
* @return true if the timer is still running, false if stopped or elapsed
*/
bool update(uint32_t now) {
if (!running) {
return false;
}
uint32_t elapsed = now - start_time;
if (elapsed > duration) {
running = false;
return false;
}
return true;
}
private:
uint32_t start_time; // When the timer was started (in milliseconds)
uint32_t duration; // How long the timer should run (in milliseconds)
bool running; // Whether the timer is currently active
};

View File

@@ -0,0 +1,58 @@
/// @file FxPacifica.ino
/// @brief Pacifica ocean effect with ScreenMap
/// @example FxPacifica.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.
//
// "Pacifica"
// Gentle, blue-green ocean waves.
// December 2019, Mark Kriegsman and Mary Corey March.
// For Dan.
//
#define FASTLED_ALLOW_INTERRUPTS 0
#include <FastLED.h>
#include "fx/1d/pacifica.h"
#include "fl/screenmap.h"
#include "defs.h" // for ENABLE_SKETCH
#if !ENABLE_SKETCH
void setup() {}
void loop() {}
#else
using namespace fl;
#define DATA_PIN 3
#define NUM_LEDS 60
#define MAX_POWER_MILLIAMPS 500
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
Pacifica pacifica(NUM_LEDS);
void setup() {
Serial.begin(115200);
ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.5f);
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screenMap);
FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_POWER_MILLIAMPS);
}
void loop() {
EVERY_N_MILLISECONDS(20) {
pacifica.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}
}
#endif // ENABLE_SKETCH

View File

@@ -0,0 +1,9 @@
#pragma once
#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC)
#define ENABLE_SKETCH 0
#else
#define ENABLE_SKETCH 1
#endif

View File

@@ -0,0 +1,42 @@
/// @file FxPride2015.ino
/// @brief Pride2015 effect with ScreenMap
/// @example FxPride2015.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 "fx/1d/pride2015.h"
#include "fl/screenmap.h"
using namespace fl;
#define DATA_PIN 3
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define NUM_LEDS 200
#define BRIGHTNESS 255
CRGB leds[NUM_LEDS];
Pride2015 pride(NUM_LEDS);
void setup() {
ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.8f);
// tell FastLED about the LED strip configuration
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screenMap)
.setDither(BRIGHTNESS < 255);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
void loop() {
pride.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}

View File

@@ -0,0 +1,136 @@
/// @file SdCard.ino
/// @brief Demonstrates playing a video on FastLED.
/// @author Zach Vorhies
///
/// 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"
#if !SKETCH_HAS_LOTS_OF_MEMORY
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
#else
#include "Arduino.h"
#include "fx/2d/noisepalette.h"
// #include "fx/2d/animartrix.hpp"
#include "fx/fx_engine.h"
#include "fx/video.h"
#include "fl/file_system.h"
#include "fl/ui.h"
#include "fl/screenmap.h"
#include "fl/file_system.h"
using namespace fl;
#define LED_PIN 2
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define FPS 60
#define CHIP_SELECT_PIN 5
#define MATRIX_WIDTH 32
#define MATRIX_HEIGHT 32
#define NUM_VIDEO_FRAMES 2 // enables interpolation with > 1 frame.
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
#define IS_SERPINTINE true
UITitle title("SDCard Demo - Mapped Video");
UIDescription description("Video data is streamed off of a SD card and displayed on a LED strip. The video data is mapped to the LED strip using a ScreenMap.");
CRGB leds[NUM_LEDS];
ScreenMap screenMap;
FileSystem filesystem;
Video video;
Video video2;
UISlider videoSpeed("Video Speed", 1.0f, -1, 2.0f, 0.01f);
UINumberField whichVideo("Which Video", 0, 0, 1);
bool gError = false;
void setup() {
Serial.begin(115200);
Serial.println("Sketch setup");
// Initialize the file system and check for errors
if (!filesystem.beginSd(CHIP_SELECT_PIN)) {
Serial.println("Failed to initialize file system.");
}
// Open video files from the SD card
video = filesystem.openVideo("data/video.rgb", NUM_LEDS, FPS, 2);
if (!video) {
FASTLED_WARN("Failed to instantiate video");
gError = true;
return;
}
video2 = filesystem.openVideo("data/color_line_bubbles.rgb", NUM_LEDS, FPS, 2);
if (!video2) {
FASTLED_WARN("Failed to instantiate video2");
gError = true;
return;
}
// Read the screen map configuration
ScreenMap screenMap;
bool ok = filesystem.readScreenMap("data/screenmap.json", "strip1", &screenMap);
if (!ok) {
Serial.println("Failed to read screen map");
gError = true;
return;
}
// Configure FastLED with the LED type, pin, and color order
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setScreenMap(screenMap);
FastLED.setBrightness(96);
Serial.println("FastLED setup done");
}
void loop() {
static bool s_first = true;
if (s_first) {
s_first = false;
Serial.println("First loop.");
}
if (gError) {
// If an error occurred, print a warning every second
EVERY_N_SECONDS(1) {
FASTLED_WARN("No loop because an error occured.");
}
return;
}
// Select the video to play based on the UI input
Video& vid = !bool(whichVideo.value()) ? video : video2;
vid.setTimeScale(videoSpeed);
// Get the current time and draw the video frame
uint32_t now = millis();
vid.draw(now, leds);
FastLED.show();
}
#endif

View File

@@ -0,0 +1,4 @@
data/ directory will appear automatically in emscripten web builds.
* The .rgb file represents uncompressed RGB video data.
* the the screenmap.json is the screenmap for "strip1"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
#include "FastLED.h"
#include "fx/1d/twinklefox.h"
using namespace fl;
#define NUM_LEDS 100
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define DATA_PIN 3
#define VOLTS 12
#define MAX_MA 4000
CRGBArray<NUM_LEDS> leds;
TwinkleFox twinkleFox(NUM_LEDS);
void setup() {
delay(3000); // safety startup delay
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_MA);
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip)
.setRgbw();
}
void loop() {
EVERY_N_SECONDS(SECONDS_PER_PALETTE) {
twinkleFox.chooseNextColorPalette(twinkleFox.targetPalette);
}
twinkleFox.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}

View File

@@ -0,0 +1,159 @@
/// @file FxWater.ino
/// @brief Water effect with XYMap
/// @example FxWater.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: sutaburosu
// based on https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
#include <FastLED.h>
#include "Arduino.h"
#include "fl/xymap.h"
using namespace fl;
#define WIDTH 32
#define HEIGHT 32
#define NUM_LEDS ((WIDTH) * (HEIGHT))
CRGB leds[NUM_LEDS];
// the water needs 2 arrays each slightly bigger than the screen
#define WATERWIDTH (WIDTH + 2)
#define WATERHEIGHT (HEIGHT + 2)
uint8_t water[2][WATERWIDTH * WATERHEIGHT];
void wu_water(uint8_t * const buf, uint16_t x, uint16_t y, uint8_t bright);
void process_water(uint8_t * src, uint8_t * dst) ;
void setup() {
Serial.begin(115200);
FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(WIDTH, HEIGHT);
}
// from: https://github.com/FastLED/FastLED/pull/202
CRGB MyColorFromPaletteExtended(const CRGBPalette16& pal, uint16_t index, uint8_t brightness, TBlendType blendType) {
// Extract the four most significant bits of the index as a palette index.
uint8_t index_4bit = (index >> 12);
// Calculate the 8-bit offset from the palette index.
uint8_t offset = (uint8_t)(index >> 4);
// Get the palette entry from the 4-bit index
const CRGB* entry = &(pal[0]) + index_4bit;
uint8_t red1 = entry->red;
uint8_t green1 = entry->green;
uint8_t blue1 = entry->blue;
uint8_t blend = offset && (blendType != NOBLEND);
if (blend) {
if (index_4bit == 15) {
entry = &(pal[0]);
} else {
entry++;
}
// Calculate the scaling factor and scaled values for the lower palette value.
uint8_t f1 = 255 - offset;
red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
// Calculate the scaled values for the neighbouring palette value.
uint8_t red2 = entry->red;
uint8_t green2 = entry->green;
uint8_t blue2 = entry->blue;
red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
cleanup_R1();
// These sums can't overflow, so no qadd8 needed.
red1 += red2;
green1 += green2;
blue1 += blue2;
}
if (brightness != 255) {
// nscale8x3_video(red1, green1, blue1, brightness);
nscale8x3(red1, green1, blue1, brightness);
}
return CRGB(red1, green1, blue1);
}
// Rectangular grid
XYMap xyMap(WIDTH, HEIGHT, false);
// map X & Y coordinates onto a horizontal serpentine matrix layout
uint16_t XY(uint8_t x, uint8_t y) {
return xyMap.mapToIndex(x, y);
}
void loop() {
// swap the src/dest buffers on each frame
static uint8_t buffer = 0;
uint8_t * const bufA = &water[buffer][0];
buffer = (buffer + 1) % 2;
uint8_t * const bufB = &water[buffer][0];
// add a moving stimulus
wu_water(bufA, beatsin16(13, 256, HEIGHT * 256), beatsin16(7, 256, WIDTH * 256), beatsin8(160, 64, 255));
// animate the water
process_water(bufA, bufB);
// display the water effect on the LEDs
uint8_t * input = bufB + WATERWIDTH - 1;
static uint16_t pal_offset = 0;
pal_offset += 256;
for (uint8_t y = 0; y < HEIGHT; y++) {
input += 2;
for (uint8_t x = 0; x < WIDTH; x++) {
leds[XY(x, y)] = MyColorFromPaletteExtended(RainbowColors_p, pal_offset + (*input++ << 8), 255, LINEARBLEND);
}
}
FastLED.show();
}
void process_water(uint8_t * src, uint8_t * dst) {
src += WATERWIDTH - 1;
dst += WATERWIDTH - 1;
for (uint8_t y = 1; y < WATERHEIGHT - 1; y++) {
src += 2; dst += 2;
for (uint8_t x = 1; x < WATERWIDTH - 1; x++) {
uint16_t t = src[-1] + src[1] + src[-WATERWIDTH] + src[WATERWIDTH];
t >>= 1;
if (dst[0] < t)
dst[0] = t - dst[0];
else
dst[0] = 0;
dst[0] -= dst[0] >> 6;
src++; dst++;
}
}
}
// draw a blob of 4 pixels with their relative brightnesses conveying sub-pixel positioning
void wu_water(uint8_t * const buf, uint16_t x, uint16_t y, uint8_t bright) {
// extract the fractional parts and derive their inverses
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
// calculate the intensities for each affected pixel
#define WU_WEIGHT(a, b) ((uint8_t)(((a) * (b) + (a) + (b)) >> 8))
uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy),
WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)
};
#undef WU_WEIGHT
// multiply the intensities by the colour, and saturating-add them to the pixels
for (uint8_t i = 0; i < 4; i++) {
uint8_t local_x = (x >> 8) + (i & 1);
uint8_t local_y = (y >> 8) + ((i >> 1) & 1);
uint16_t xy = WATERWIDTH * local_y + local_x;
if (xy >= WATERWIDTH * WATERHEIGHT) continue;
uint16_t this_bright = bright * wu[i];
buf[xy] = qadd8(buf[xy], this_bright >> 8);
}
}

View File

@@ -0,0 +1,6 @@
#include "fl/sketch_macros.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "./FxWater.h"
#else
#include "platforms/sketch_fake.hpp"
#endif

View File

@@ -0,0 +1,46 @@
/*
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 sketch demonstrates a 2D wave simulation with multiple layers and blending effects.
It creates ripple effects that propagate across the LED matrix, similar to water waves.
The demo includes two wave layers (upper and lower) with different colors and properties,
which are blended together to create complex visual effects.
*/
#include <Arduino.h> // Core Arduino functionality
#include <FastLED.h> // Main FastLED library for controlling LEDs
#include "fl/sketch_macros.h"
#if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
void setup() {}
void loop() {}
#else
#include "wavefx.h"
using namespace fl; // Use the FastLED namespace for convenience
void setup() {
Serial.begin(115200); // Initialize serial communication for debugging
wavefx_setup();
}
void loop() {
// The main program loop that runs continuously
wavefx_loop();
}
#endif

View File

@@ -0,0 +1,445 @@
/*
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 sketch demonstrates a 2D wave simulation with multiple layers and blending effects.
It creates ripple effects that propagate across the LED matrix, similar to water waves.
The demo includes two wave layers (upper and lower) with different colors and properties,
which are blended together to create complex visual effects.
*/
#include <Arduino.h> // Core Arduino functionality
#include <FastLED.h> // Main FastLED library for controlling LEDs
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "fl/math_macros.h" // Math helper functions and macros
#include "fl/time_alpha.h" // Time-based alpha/transition effects
#include "fl/ui.h" // UI components for the FastLED web compiler
#include "fx/2d/blend.h" // 2D blending effects between layers
#include "fx/2d/wave.h" // 2D wave simulation
#include "wavefx.h" // Header file for this sketch
using namespace fl; // Use the FastLED namespace for convenience
// Array to hold all LED color values - one CRGB struct per LED
CRGB leds[NUM_LEDS];
// UI elements that appear in the FastLED web compiler interface:
UITitle title("FxWave2D Demo");
UIDescription description("Advanced layered and blended wave effects.");
UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop)
// Main control UI elements:
UIButton button("Trigger"); // Button to trigger a single ripple
UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect
UICheckbox autoTrigger("Auto Trigger", true); // Enable/disable automatic ripple triggering
UISlider triggerSpeed("Trigger Speed", .5f, 0.0f, 1.0f, 0.01f); // Controls how frequently auto-triggers happen (lower = faster)
UICheckbox easeModeSqrt("Ease Mode Sqrt", false); // Changes how wave heights are calculated (sqrt gives more natural waves)
UICheckbox useChangeGrid("Use Change Grid", false); // Enable performance optimization (reduces visual oscillation)
UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall blur amount for all layers
UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); // Controls how many times blur is applied (more = smoother but slower)
UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); // Controls anti-aliasing quality (higher = better quality but more CPU)
// Upper wave layer controls:
UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates
UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f); // How quickly the upper wave loses energy
UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true); // If true, waves only go positive (not negative)
UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1); // Blur amount for upper wave layer
UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1); // Blur passes for upper wave layer
// Lower wave layer controls:
UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f); // How fast the lower wave propagates
UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f); // How quickly the lower wave loses energy
UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true); // If true, waves only go positive (not negative)
UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1); // Blur amount for lower wave layer
UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1); // Blur passes for lower wave layer
// Fancy effect controls (for the cross-shaped effect):
UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1); // Speed of the fancy effect animation
UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1); // Intensity/height of the fancy effect waves
UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f); // Width of the fancy effect lines
// Help text explaining the Use Change Grid feature
UIHelp changeGridHelp("Use Change Grid preserves the set point over multiple iterations to ensure more stable results across simulation resolutions. However, turning it off may result in more dramatic effects and saves memory.");
// Color palettes define the gradient of colors used for the wave effects
// Each entry has the format: position (0-255), R, G, B
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
0, 0, 0, 0, // Black (lowest wave height)
32, 0, 0, 70, // Dark blue (low wave height)
128, 20, 57, 255, // Electric blue (medium wave height)
255, 255, 255, 255 // White (maximum wave height)
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
0, 0, 0, 0, // Black (lowest wave height)
8, 128, 64, 64, // Green with red tint (very low wave height)
16, 255, 222, 222, // Pinkish red (low wave height)
64, 255, 255, 255, // White (medium wave height)
255, 255, 255, 255 // White (maximum wave height)
};
// Create mappings between 1D array positions and 2D x,y coordinates
XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); // For the actual LED output (may be serpentine)
XYMap xyRect(WIDTH, HEIGHT, false); // For the wave simulation (always rectangular grid)
// Create default configuration for the lower wave layer
WaveFx::Args CreateArgsLower() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
out.half_duplex = true; // Only positive waves (no negative values)
out.auto_updates = true; // Automatically update the simulation each frame
out.speed = 0.18f; // Wave propagation speed
out.dampening = 9.0f; // How quickly waves lose energy
out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricBlueFirePal); // Color palette for this wave
return out;
}
// Create default configuration for the upper wave layer
WaveFx::Args CreateArgsUpper() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
out.half_duplex = true; // Only positive waves (no negative values)
out.auto_updates = true; // Automatically update the simulation each frame
out.speed = 0.25f; // Wave propagation speed (faster than lower)
out.dampening = 3.0f; // How quickly waves lose energy (less than lower)
out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricGreenFirePal); // Color palette for this wave
return out;
}
// Create the two wave simulation layers with their default configurations
WaveFx waveFxLower(xyRect, CreateArgsLower()); // Lower/background wave layer (blue)
WaveFx waveFxUpper(xyRect, CreateArgsUpper()); // Upper/foreground wave layer (green/red)
// Create a blender that will combine the two wave layers
Blend2d fxBlend(xyMap);
// Convert the UI slider value to the appropriate SuperSample enum value
// SuperSample controls the quality of the wave simulation (higher = better quality but more CPU)
SuperSample getSuperSample() {
switch (int(superSample)) {
case 0:
return SuperSample::SUPER_SAMPLE_NONE; // No supersampling (fastest, lowest quality)
case 1:
return SuperSample::SUPER_SAMPLE_2X; // 2x supersampling (2x2 grid = 4 samples per pixel)
case 2:
return SuperSample::SUPER_SAMPLE_4X; // 4x supersampling (4x4 grid = 16 samples per pixel)
case 3:
return SuperSample::SUPER_SAMPLE_8X; // 8x supersampling (8x8 grid = 64 samples per pixel, slowest)
default:
return SuperSample::SUPER_SAMPLE_NONE; // Default fallback
}
}
// Create a ripple effect at a random position within the central area of the display
void triggerRipple() {
// Define a margin percentage to keep ripples away from the edges
float perc = .15f;
// Calculate the boundaries for the ripple (15% from each edge)
uint8_t min_x = perc * WIDTH; // Left boundary
uint8_t max_x = (1 - perc) * WIDTH; // Right boundary
uint8_t min_y = perc * HEIGHT; // Top boundary
uint8_t max_y = (1 - perc) * HEIGHT; // Bottom boundary
// Generate a random position within these boundaries
int x = random(min_x, max_x);
int y = random(min_y, max_y);
// Set a wave peak at this position in both wave layers
// The value 1.0 represents the maximum height of the wave
waveFxLower.setf(x, y, 1); // Create ripple in lower layer
waveFxUpper.setf(x, y, 1); // Create ripple in upper layer
}
// Create a fancy cross-shaped effect that expands from the center
void applyFancyEffect(uint32_t now, bool button_active) {
// Calculate the total animation duration based on the speed slider
// Higher fancySpeed value = shorter duration (faster animation)
uint32_t total =
map(fancySpeed.as<uint32_t>(), 0, fancySpeed.getMax(), 1000, 100);
// Create a static TimeRamp to manage the animation timing
// TimeRamp handles the transition from start to end over time
static TimeRamp pointTransition = TimeRamp(total, 0, 0);
// If the button is active, start/restart the animation
if (button_active) {
pointTransition.trigger(now, total, 0, 0);
}
// If the animation isn't currently active, exit early
if (!pointTransition.isActive(now)) {
// no need to draw
return;
}
// Find the center of the display
int mid_x = WIDTH / 2;
int mid_y = HEIGHT / 2;
// Calculate the maximum distance from center (half the width)
int amount = WIDTH / 2;
// Calculate the start and end coordinates for the cross
int start_x = mid_x - amount; // Leftmost point
int end_x = mid_x + amount; // Rightmost point
int start_y = mid_y - amount; // Topmost point
int end_y = mid_y + amount; // Bottommost point
// Get the current animation progress (0-255)
int curr_alpha = pointTransition.update8(now);
// Map the animation progress to the four points of the expanding cross
// As curr_alpha increases from 0 to 255, these points move from center to edges
int left_x = map(curr_alpha, 0, 255, mid_x, start_x); // Center to left
int down_y = map(curr_alpha, 0, 255, mid_y, start_y); // Center to top
int right_x = map(curr_alpha, 0, 255, mid_x, end_x); // Center to right
int up_y = map(curr_alpha, 0, 255, mid_y, end_y); // Center to bottom
// Convert the 0-255 alpha to 0.0-1.0 range
float curr_alpha_f = curr_alpha / 255.0f;
// Calculate the wave height value - starts high and decreases as animation progresses
// This makes the waves stronger at the beginning of the animation
float valuef = (1.0f - curr_alpha_f) * fancyIntensity.value() / 255.0f;
// Calculate the width of the cross lines
int span = fancyParticleSpan.value() * WIDTH;
// Add wave energy along the four expanding lines of the cross
// Each line is a horizontal or vertical span of pixels
// Left-moving horizontal line
for (int x = left_x - span; x < left_x + span; x++) {
waveFxLower.addf(x, mid_y, valuef); // Add to lower layer
waveFxUpper.addf(x, mid_y, valuef); // Add to upper layer
}
// Right-moving horizontal line
for (int x = right_x - span; x < right_x + span; x++) {
waveFxLower.addf(x, mid_y, valuef);
waveFxUpper.addf(x, mid_y, valuef);
}
// Downward-moving vertical line
for (int y = down_y - span; y < down_y + span; y++) {
waveFxLower.addf(mid_x, y, valuef);
waveFxUpper.addf(mid_x, y, valuef);
}
// Upward-moving vertical line
for (int y = up_y - span; y < up_y + span; y++) {
waveFxLower.addf(mid_x, y, valuef);
waveFxUpper.addf(mid_x, y, valuef);
}
}
// Structure to hold the state of UI buttons
struct ui_state {
bool button = false; // Regular ripple button state
bool bigButton = false; // Fancy effect button state
};
// Apply all UI settings to the wave effects and return button states
ui_state ui() {
// Set the easing function based on the checkbox
// Easing controls how wave heights are calculated:
// - LINEAR: Simple linear mapping (sharper waves)
// - SQRT: Square root mapping (more natural, rounded waves)
U8EasingFunction easeMode = easeModeSqrt
? U8EasingFunction::WAVE_U8_MODE_SQRT
: U8EasingFunction::WAVE_U8_MODE_LINEAR;
// Apply all settings from UI controls to the lower wave layer
waveFxLower.setSpeed(speedLower); // Wave propagation speed
waveFxLower.setDampening(dampeningLower); // How quickly waves lose energy
waveFxLower.setHalfDuplex(halfDuplexLower); // Whether waves can go negative
waveFxLower.setSuperSample(getSuperSample()); // Anti-aliasing quality
waveFxLower.setEasingMode(easeMode); // Wave height calculation method
waveFxLower.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality
// Apply all settings from UI controls to the upper wave layer
waveFxUpper.setSpeed(speedUpper); // Wave propagation speed
waveFxUpper.setDampening(dampeningUpper); // How quickly waves lose energy
waveFxUpper.setHalfDuplex(halfDuplexUpper); // Whether waves can go negative
waveFxUpper.setSuperSample(getSuperSample()); // Anti-aliasing quality
waveFxUpper.setEasingMode(easeMode); // Wave height calculation method
waveFxUpper.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality
// Apply global blur settings to the blender
fxBlend.setGlobalBlurAmount(blurAmount); // Overall blur strength
fxBlend.setGlobalBlurPasses(blurPasses); // Number of blur passes
// Create parameter structures for each wave layer's blur settings
fl::Blend2dParams lower_params;
lower_params.blur_amount = blurAmountLower; // Blur amount for lower layer
lower_params.blur_passes = blurPassesLower; // Blur passes for lower layer
fl::Blend2dParams upper_params;
upper_params.blur_amount = blurAmountUpper; // Blur amount for upper layer
upper_params.blur_passes = blurPassesUpper; // Blur passes for upper layer
// Apply the layer-specific blur parameters
fxBlend.setParams(waveFxLower, lower_params);
fxBlend.setParams(waveFxUpper, upper_params);
// Return the current state of the UI buttons
ui_state state;
state.button = button; // Regular ripple button
state.bigButton = buttonFancy; // Fancy effect button
return state;
}
// Handle automatic triggering of ripples at random intervals
void processAutoTrigger(uint32_t now) {
// Static variable to remember when the next auto-trigger should happen
static uint32_t nextTrigger = 0;
// Calculate time until next trigger
uint32_t trigger_delta = nextTrigger - now;
// Handle timer overflow (happens after ~49 days of continuous running)
if (trigger_delta > 10000) {
// If the delta is suspiciously large, we probably rolled over
trigger_delta = 0;
}
// Only proceed if auto-trigger is enabled
if (autoTrigger) {
// Check if it's time for the next trigger
if (now >= nextTrigger) {
// Create a ripple
triggerRipple();
// Calculate the next trigger time based on the speed slider
// Invert the speed value so higher slider = faster triggers
float speed = 1.0f - triggerSpeed.value();
// Calculate min and max random intervals
// Higher speed = shorter intervals between triggers
uint32_t min_rand = 400 * speed; // Minimum interval (milliseconds)
uint32_t max_rand = 2000 * speed; // Maximum interval (milliseconds)
// Ensure min is actually less than max (handles edge cases)
uint32_t min = MIN(min_rand, max_rand);
uint32_t max = MAX(min_rand, max_rand);
// Ensure min and max aren't equal (would cause random() to crash)
if (min == max) {
max += 1;
}
// Schedule the next trigger at a random time in the future
nextTrigger = now + random(min, max);
}
}
}
void wavefx_setup() {
// Create a screen map for visualization in the FastLED web compiler
auto screenmap = xyMap.toScreenMap();
screenmap.setDiameter(.2); // Set the size of the LEDs in the visualization
// Initialize the LED strip:
// - NEOPIXEL is the LED type
// - 2 is the data pin number (for real hardware)
// - setScreenMap connects our 2D coordinate system to the 1D LED array
FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap);
// Set up UI groupings
// Main Controls
title.setGroup("Main Controls");
description.setGroup("Main Controls");
button.setGroup("Main Controls");
buttonFancy.setGroup("Main Controls");
autoTrigger.setGroup("Main Controls");
triggerSpeed.setGroup("Main Controls");
// Global Settings
xCyclical.setGroup("Global Settings");
easeModeSqrt.setGroup("Global Settings");
useChangeGrid.setGroup("Global Settings");
blurAmount.setGroup("Global Settings");
blurPasses.setGroup("Global Settings");
superSample.setGroup("Global Settings");
// Upper Wave Layer
speedUpper.setGroup("Upper Wave Layer");
dampeningUpper.setGroup("Upper Wave Layer");
halfDuplexUpper.setGroup("Upper Wave Layer");
blurAmountUpper.setGroup("Upper Wave Layer");
blurPassesUpper.setGroup("Upper Wave Layer");
// Lower Wave Layer
speedLower.setGroup("Lower Wave Layer");
dampeningLower.setGroup("Lower Wave Layer");
halfDuplexLower.setGroup("Lower Wave Layer");
blurAmountLower.setGroup("Lower Wave Layer");
blurPassesLower.setGroup("Lower Wave Layer");
// Fancy Effects
fancySpeed.setGroup("Fancy Effects");
fancyIntensity.setGroup("Fancy Effects");
fancyParticleSpan.setGroup("Fancy Effects");
// Add both wave layers to the blender
// The order matters - lower layer is added first (background)
fxBlend.add(waveFxLower);
fxBlend.add(waveFxUpper);
}
void wavefx_loop() {
// The main program loop that runs continuously
// Get the current time in milliseconds
uint32_t now = millis();
// set the x cyclical
waveFxLower.setXCylindrical(xCyclical.value()); // Set whether lower wave wraps around x-axis
// Apply all UI settings and get button states
ui_state state = ui();
// Check if the regular ripple button was pressed
if (state.button) {
triggerRipple(); // Create a single ripple
}
// Apply the fancy cross effect if its button is pressed
applyFancyEffect(now, state.bigButton);
// Handle automatic triggering of ripples
processAutoTrigger(now);
// Create a drawing context with the current time and LED array
Fx::DrawContext ctx(now, leds);
// Draw the blended result of both wave layers to the LED array
fxBlend.draw(ctx);
// Send the color data to the actual LEDs
FastLED.show();
}
#endif // SKETCH_HAS_LOTS_OF_MEMORY

View File

@@ -0,0 +1,33 @@
#pragma once
/*
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 sketch demonstrates a 2D wave simulation with multiple layers and blending effects.
It creates ripple effects that propagate across the LED matrix, similar to water waves.
The demo includes two wave layers (upper and lower) with different colors and properties,
which are blended together to create complex visual effects.
*/
// Define the dimensions of our LED matrix
#define HEIGHT 64 // Number of rows in the matrix
#define WIDTH 64 // Number of columns in the matrix
#define NUM_LEDS ((WIDTH) * (HEIGHT)) // Total number of LEDs
#define IS_SERPINTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
void wavefx_setup();
void wavefx_loop();

View File

@@ -0,0 +1,63 @@
/// @file HD107.ino
/// @brief Example showing how to use the HD107 and HD which has built in gamma correction.
/// This simply the HD107HD examles but with this chipsets.
/// @see HD107HD.ino.
#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 HD107HD
// 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<HD107HD, STRIP_0_DATA_PIN, STRIP_0_CLOCK_PIN, RGB>(leds_hd, NUM_LEDS);
FastLED.addLeds<HD107, 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 HD107HD 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,62 @@
/// @file HSVTest.ino
/// @brief Test HSV color space conversions
/// @example HSVTest.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"
#define NUM_LEDS 5
#ifndef PIN_DATA
#define PIN_DATA 1 // Universally available pin
#endif
#ifndef PIN_CLOCK
#define PIN_CLOCK 2 // Universally available pin
#endif
CRGB leds[NUM_LEDS];
UISlider hueSlider("Hue", 128, 0, 255, 1);
UISlider saturationSlider("Saturation", 255, 0, 255, 1);
UISlider valueSlider("Value", 255, 0, 255, 1);
UICheckbox autoHue("Auto Hue", true);
// Group related HSV control UI elements using UIGroup template multi-argument constructor
UIGroup hsvControls("HSV Controls", hueSlider, saturationSlider, valueSlider, autoHue);
void setup() {
//FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
// fl::ScreenMap screenMap(NUM_LEDS);
FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip);
}
void loop() {
uint8_t hue = hueSlider.value();
uint8_t saturation = saturationSlider.value();
uint8_t value = valueSlider.value();
if (autoHue.value()) {
uint32_t now = millis();
uint32_t hue_offset = (now / 100) % 256;
hue = hue_offset;
hueSlider.setValue(hue);
}
CHSV hsv(hue, saturation, value);
leds[0] = hsv2rgb_spectrum(hsv);
leds[2] = hsv2rgb_rainbow(hsv);
leds[4] = hsv2rgb_fullspectrum(hsv);
FastLED.show();
}

View File

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

View File

@@ -0,0 +1,7 @@
#include "fl/sketch_macros.h"
#if SKETCH_HAS_LOTS_OF_MEMORY
#include "JsonSketch.h"
#else
void setup() {}
void loop() {}
#endif // SKETCH_HAS_LOTS_OF_MEMORY

View File

@@ -0,0 +1,147 @@
/// @file JsonIdeal.ino
/// @brief Demonstrates an ideal fluid JSON API with FastLED
///
/// This example showcases the proposed ideal JSON API for FastLED,
/// emphasizing clean syntax, type safety, default values, and robust
/// handling of missing fields.
#include <Arduino.h>
#include "FastLED.h"
#include "fl/json.h" // Assumed ideal JSON API header
#define NUM_LEDS 100
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
Serial.begin(115200);
// Initialize FastLED
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(64);
Serial.println("FastLED Ideal JSON API Demo Starting...");
// Example JSON string with LED configuration
const char* configJson = R"({
"strip": {
"num_leds": 150,
"pin": 5,
"type": "WS2812B",
"brightness": 200
},
"effects": {
"current": "rainbow",
"speed": 75
},
"animation_settings": {
"duration_ms": 5000,
"loop": true
}
})";
// NEW: Parse using ideal API
fl::Json json = fl::Json::parse(configJson);
if (json.has_value()) {
Serial.println("JSON parsed successfully with ideal API!");
// NEW: Clean syntax with default values - no more verbose error checking!
int numLeds = json["strip"]["num_leds"] | 100; // Gets 150, or 100 if missing
int pin = json["strip"]["pin"] | 3; // Gets 5, or 3 if missing
fl::string type = json["strip"]["type"] | fl::string("WS2812"); // Gets "WS2812B"
int brightness = json["strip"]["brightness"] | 64; // Gets 200, or 64 if missing
// Safe access to missing values - no crashes!
int missing = json["non_existent"]["missing"] | 999; // Gets 999
Serial.println("LED Strip Configuration:");
Serial.print(" LEDs: "); Serial.println(numLeds);
Serial.print(" Pin: "); Serial.println(pin);
Serial.print(" Type: "); Serial.println(type.c_str());
Serial.print(" Brightness: "); Serial.println(brightness);
Serial.print(" Missing field default: "); Serial.println(missing);
// Effect configuration with safe defaults
fl::string effect = json["effects"]["current"] | fl::string("solid");
int speed = json["effects"]["speed"] | 50;
Serial.println("Effect Configuration:");
Serial.print(" Current: "); Serial.println(effect.c_str());
Serial.print(" Speed: "); Serial.println(speed);
// Accessing nested objects with defaults
long duration = json["animation_settings"]["duration_ms"] | 1000;
bool loop = json["animation_settings"]["loop"] | false;
Serial.println("Animation Settings:");
Serial.print(" Duration (ms): "); Serial.println(static_cast<int32_t>(duration));
Serial.print(" Loop: "); Serial.println(loop ? "true" : "false");
Serial.println("\n=== NEW ERGONOMIC API DEMONSTRATION ===");
// Example JSON with mixed data types including strings that can be converted
const char* mixedJson = R"({
"config": {
"brightness": "128",
"timeout": "5.5",
"enabled": true,
"name": "LED Strip"
}
})";
fl::Json config = fl::Json::parse(mixedJson);
Serial.println("\nThree New Conversion Methods:");
// Method 1: try_as<T>() - Explicit optional handling
Serial.println("\n1. try_as<T>() - When you need explicit error handling:");
auto maybeBrightness = config["config"]["brightness"].try_as<int>();
if (maybeBrightness.has_value()) {
Serial.print(" Brightness converted from string: ");
Serial.println(*maybeBrightness);
} else {
Serial.println(" Brightness conversion failed");
}
// Method 2: value<T>() - Direct conversion with sensible defaults
Serial.println("\n2. value<T>() - When you want defaults and don't care about failure:");
int brightnessDirect = config["config"]["brightness"].value<int>();
int missingDirect = config["missing_field"].value<int>();
Serial.print(" Brightness (from string): ");
Serial.println(brightnessDirect);
Serial.print(" Missing field (default 0): ");
Serial.println(missingDirect);
// Method 3: as_or<T>(default) - Custom defaults
Serial.println("\n3. as_or<T>(default) - When you want custom defaults:");
int customBrightness = config["config"]["brightness"].as_or<int>(255);
int customMissing = config["missing_field"].as_or<int>(100);
double timeout = config["config"]["timeout"].as_or<double>(10.0);
Serial.print(" Brightness with custom default: ");
Serial.println(customBrightness);
Serial.print(" Missing with custom default: ");
Serial.println(customMissing);
Serial.print(" Timeout (string to double): ");
Serial.println(timeout);
Serial.println("\nNew API provides:");
Serial.println(" ✓ Type safety with automatic string-to-number conversion");
Serial.println(" ✓ Three distinct patterns for different use cases");
Serial.println(" ✓ Backward compatibility with existing as<T>() API");
Serial.println(" ✓ Clean, readable syntax");
Serial.println(" ✓ Significantly less code for common operations");
} else {
Serial.println("JSON parsing failed with ideal API");
}
}
void loop() {
// Simple LED pattern to show something is happening
static uint8_t hue = 0;
fill_rainbow(leds, NUM_LEDS, hue++, 7);
FastLED.show();
delay(50);
}

View File

@@ -0,0 +1,259 @@
/// This is a work in progress showcasing a complex MIDI keyboard
/// visualizer.
/// To run this compiler use
/// > pip install fastled
/// Then go to your sketch directory and run
/// > fastled
#include "shared/defs.h"
#if !ENABLE_SKETCH
// avr can't compile this, neither can the esp8266.
void setup() {}
void loop() {}
#else
//#define DEBUG_PAINTER
//#define DEBUG_KEYBOARD 1
// Repeated keyboard presses in the main loop
#define DEBUG_FORCED_KEYBOARD
// #define DEBUG_MIDI_KEY 72
#define MIDI_SERIAL_PORT Serial1
#define FASTLED_UI // Brings in the UI components.
#include "FastLED.h"
// H
#include <Arduino.h>
#include "shared/Keyboard.h"
#include "shared/color.h"
#include "shared/led_layout_array.h"
#include "shared/Keyboard.h"
#include "shared/Painter.h"
#include "shared/settings.h"
#include "arduino/LedRopeTCL.h"
#include "arduino/ui_state.h"
#include "shared/dprint.h"
#include "fl/dbg.h"
#include "fl/ui.h"
#include "fl/unused.h"
// Spoof the midi library so it thinks it's running on an arduino.
//#ifndef ARDUINO
//#define ARDUINO 1
//#endif
#ifdef MIDI_AUTO_INSTANCIATE
#undef MIDI_AUTO_INSTANCIATE
#define MIDI_AUTO_INSTANCIATE 0
#endif
#include "arduino/MIDI.h"
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MY_MIDI);
FASTLED_TITLE("Luminescent Grand");
FASTLED_DESCRIPTION("A midi keyboard visualizer.");
/////////////////////////////////////////////////////////
// Light rope and keyboard.
LedRopeTCL led_rope(kNumKeys);
KeyboardState keyboard;
////////////////////////////////////
// Called when the note is pressed.
// Input:
// channel - Ignored.
// midi_note - Value between 21-108 which maps to the keyboard keys.
// velocity - Value between 0-127
void HandleNoteOn(byte channel, byte midi_note, byte velocity) {
FL_UNUSED(channel);
FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity));
keyboard.HandleNoteOn(midi_note, velocity, color_selector.curr_val(), millis());
}
/////////////////////////////////////////////////////////
// Called when the note is released.
// Input:
// channel - Ignored.
// midi_note - Value between 21-108 which maps to the keyboard keys.
// velocity - Value between 0-127
void HandleNoteOff(byte channel, byte midi_note, byte velocity) {
FL_UNUSED(channel);
FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity));
keyboard.HandleNoteOff(midi_note, velocity, millis());
}
/////////////////////////////////////////////////////////
// This is uninmplemented because the test keyboard didn't
// have this functionality. Right now the only thing it does is
// print out that the key was pressed.
void HandleAfterTouchPoly(byte channel, byte note, byte pressure) {
FL_UNUSED(channel);
keyboard.HandleAfterTouchPoly(note, pressure);
}
/////////////////////////////////////////////////////////
// Detects whether the foot pedal has been touched.
void HandleControlChange(byte channel, byte d1, byte d2) {
FL_UNUSED(channel);
keyboard.HandleControlChange(d1, d2);
}
void HandleAfterTouchChannel(byte channel, byte pressure) {
FL_UNUSED(channel);
FL_UNUSED(pressure);
#if 0 // Disabled for now.
if (0 == pressure) {
for (int i = 0; i < kNumKeys; ++i) {
Key& key = keyboard.keys_[i];
key.SetOff();
}
}
#endif
}
/////////////////////////////////////////////////////////
// Called once when the app starts.
void setup() {
FASTLED_DBG("setup");
// Serial port for logging.
Serial.begin(57600);
//start serial with midi baudrate 31250
// Initiate MIDI communications, listen to all channels
MY_MIDI.begin(MIDI_CHANNEL_OMNI);
// Connect the HandleNoteOn function to the library, so it is called upon reception of a NoteOn.
MY_MIDI.setHandleNoteOn(HandleNoteOn);
MY_MIDI.setHandleNoteOff(HandleNoteOff);
MY_MIDI.setHandleAfterTouchPoly(HandleAfterTouchPoly);
MY_MIDI.setHandleAfterTouchChannel(HandleAfterTouchChannel);
MY_MIDI.setHandleControlChange(HandleControlChange);
ui_init();
}
void DbgDoSimulatedKeyboardPress() {
#ifdef DEBUG_FORCED_KEYBOARD
static uint32_t start_time = 0;
static bool toggle = 0;
const uint32_t time_on = 25;
const uint32_t time_off = 500;
// Just force it on whenever this function is called.
is_debugging = true;
uint32_t now = millis();
uint32_t delta_time = now - start_time;
uint32_t threshold_time = toggle ? time_off : time_on;
if (delta_time < threshold_time) {
return;
}
int random_key = random(0, 88);
start_time = now;
if (toggle) {
HandleNoteOn(0, random_key, 64);
} else {
HandleNoteOff(0, random_key, 82);
}
toggle = !toggle;
#endif
}
/////////////////////////////////////////////////////////;p
// Repeatedly called by the app.
void loop() {
//FASTLED_DBG("loop");
// Calculate dt.
static uint32_t s_prev_time = 0;
uint32_t prev_time = 0;
FASTLED_UNUSED(prev_time); // actually used in perf tests.
uint32_t now_ms = millis();
uint32_t delta_ms = now_ms - s_prev_time;
s_prev_time = now_ms;
if (!is_debugging) {
if (Serial.available() > 0) {
int v = Serial.read();
if (v == 'd') {
is_debugging = true;
}
}
}
DbgDoSimulatedKeyboardPress();
const unsigned long start_time = millis();
// Each frame we call the midi processor 100 times to make sure that all notes
// are processed.
for (int i = 0; i < 100; ++i) {
MY_MIDI.read();
}
const unsigned long midi_time = millis() - start_time;
// Updates keyboard: releases sustained keys that.
const uint32_t keyboard_time_start = millis();
// This is kind of a hack... but give the keyboard a future time
// so that all keys just pressed get a value > 0 for their time
// durations.
keyboard.Update(now_ms + delta_ms, delta_ms);
const uint32_t keyboard_delta_time = millis() - keyboard_time_start;
ui_state ui_st = ui_update(now_ms, delta_ms);
//dprint("vis selector = ");
//dprintln(vis_state);
// These int values are for desting the performance of the
// app. If the app ever runs slow then set kShowFps to 1
// in the settings.h file.
const unsigned long start_painting = millis();
FASTLED_UNUSED(start_painting);
// Paints the keyboard using the led_rope.
Painter::VisState which_vis = Painter::VisState(ui_st.which_visualizer);
Painter::Paint(now_ms, delta_ms, which_vis, &keyboard, &led_rope);
const unsigned long paint_time = millis() - start_time;
const unsigned long total_time = midi_time + paint_time + keyboard_delta_time;
if (kShowFps) {
float fps = 1.0f/(float(total_time) / 1000.f);
Serial.print("fps - "); Serial.println(fps);
Serial.print("midi time - "); Serial.println(midi_time);
Serial.print("keyboard update time - "); Serial.println(keyboard_delta_time);
Serial.print("draw & paint time - "); Serial.println(paint_time);
}
EVERY_N_SECONDS(1) {
FASTLED_DBG("is_debugging = " << is_debugging);
}
FastLED.show();
}
#endif // __AVR__

View File

@@ -0,0 +1,179 @@
// Copyleft (c) 2012, Zach Vorhies
// Public domain, no rights reserved.
// This object holds a frame buffer and effects can be applied. This is a higher level
// object than the TCL class which this object uses for drawing.
//#include "./tcl.h"
#include <Arduino.h>
#include "../shared/color.h"
#include "../shared/framebuffer.h"
#include "../shared/settings.h"
#include "./LedRopeTCL.h"
#include "../shared/led_layout_array.h"
#include "FastLED.h"
#include "fl/dbg.h"
#include "fl/ui.h"
using namespace fl;
#define CHIPSET WS2812
#define PIN_DATA 1
#define PIN_CLOCK 2
namespace {
UIButton buttonAllWhite("All white");
ScreenMap init_screenmap() {
LedColumns cols = LedLayoutArray();
const int length = cols.length;
int sum = 0;
for (int i = 0; i < length; ++i) {
sum += cols.array[i];
}
ScreenMap screen_map(sum, 0.8f);
int curr_idx = 0;
for (int i = 0; i < length; ++i) {
int n = cols.array[i];
int stagger = i % 2 ? 4 : 0;
for (int j = 0; j < n; ++j) {
fl::vec2f xy(i*4, j*8 + stagger);
screen_map.set(curr_idx++, xy);
}
}
return screen_map;
}
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::PreDrawSetup() {
if (!lazy_initialized_) {
// This used to do something, now it does nothing.
lazy_initialized_ = true;
}
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawBeginDraw() {
PreDrawSetup();
led_buffer_.clear();
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixel(const Color3i& c) {
RawDrawPixel(c.r_, c.g_, c.b_);
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixel(byte r, byte g, byte b) {
if (led_buffer_.size() >= mScreenMap.getLength()) {
return;
}
if (buttonAllWhite.isPressed()) {
r = 0xff;
g = 0xff;
b = 0xff;
}
CRGB c(r, g, b);
led_buffer_.push_back(CRGB(r, g, b));
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixels(const Color3i& c, int n) {
for (int i = 0; i < n; ++i) {
RawDrawPixel(c);
}
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::set_draw_offset(int val) {
draw_offset_ = constrain(val, 0, frame_buffer_.length());
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawCommitDraw() {
FASTLED_WARN("\n\n############## COMMIT DRAW ################\n\n");
if (!controller_added_) {
controller_added_ = true;
CRGB* leds = led_buffer_.data();
size_t n_leds = led_buffer_.size();
FastLED.addLeds<APA102, PIN_DATA, PIN_CLOCK>(leds, n_leds).setScreenMap(mScreenMap);
}
FASTLED_WARN("FastLED.show");
FastLED.show();
}
///////////////////////////////////////////////////////////////////////////////
LedRopeTCL::LedRopeTCL(int n_pixels)
: draw_offset_(0), lazy_initialized_(false), frame_buffer_(n_pixels) {
mScreenMap = init_screenmap();
led_buffer_.reserve(mScreenMap.getLength());
}
///////////////////////////////////////////////////////////////////////////////
LedRopeTCL::~LedRopeTCL() {
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::Draw() {
RawBeginDraw();
const Color3i* begin = GetIterator(0);
const Color3i* middle = GetIterator(draw_offset_);
const Color3i* end = GetIterator(length() - 1);
for (const Color3i* it = middle; it != end; ++it) {
RawDrawPixel(*it);
}
for (const Color3i* it = begin; it != middle; ++it) {
RawDrawPixel(*it);
}
RawCommitDraw();
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::DrawSequentialRepeat(int repeat) {
RawBeginDraw();
const Color3i* begin = GetIterator(0);
const Color3i* middle = GetIterator(draw_offset_);
const Color3i* end = GetIterator(length());
for (const Color3i* it = middle; it != end; ++it) {
for (int i = 0; i < repeat; ++i) {
RawDrawPixel(it->r_, it->g_, it->b_);
}
}
for (const Color3i* it = begin; it != middle; ++it) {
for (int i = 0; i < repeat; ++i) {
RawDrawPixel(it->r_, it->g_, it->b_);
}
}
RawCommitDraw();
}
///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::DrawRepeat(const int* value_array, int array_length) {
RawBeginDraw();
// Make sure that the number of colors to repeat does not exceed the length
// of the rope.
const int len = MIN(array_length, frame_buffer_.length());
for (int i = 0; i < len; ++i) {
const Color3i* cur_color = GetIterator(i); // Current color.
const int repeat_count = value_array[i];
// Repeatedly send the same color down the led rope.
for (int k = 0; k < repeat_count; ++k) {
RawDrawPixel(cur_color->r_, cur_color->g_, cur_color->b_);
}
}
// Finish the drawing.
RawCommitDraw();
}

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