Signed-off-by: myperworld <myperworld@126.com>
This commit is contained in:
1 parent
0986832fd1
commit
aacd052c06
3403 files changed
+597376
-12
No files matched your search
@@ -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.**
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_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
|
||||
@@ -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__
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
+50
@@ -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.
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -0,0 +1,564 @@
|
||||
/// @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)
|
||||
FL_DISABLE_WARNING(implicit-float-conversion)
|
||||
FL_DISABLE_WARNING(implicit-int-float-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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
/// @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
|
||||
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() {
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
+47
@@ -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
|
||||
]}
|
||||
)";
|
||||
+6681
File diff suppressed because it is too large.
Load diff
+1
@@ -0,0 +1 @@
|
||||
{"0":{},"5":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Candy Cane"} ,"4":{"playlist":{"ps":[1,6,9,14,10,3,15,11,20,7,17,12,19,13,18,2],"dur":[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],"transition":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"repeat":0,"end":0,"r":1},"on":true,"n":"Random","ql":"🔀"},"27":{"playlist":{"ps":[11,12,15,3,9,2,6,7,18,14,10,1,19,20],"dur":[300,300,300,300,300,300,300,300,300,300,300,300,300,300],"transition":[0,0,0,0,0,0,0,0,0,0,0,0,0,0],"repeat":0,"end":0,"r":1},"on":true,"n":"Random (Non Audio Reactive)","ql":"🔇"} ,"26":{"playlist":{"ps":[23,25,22,21,13,24,17],"dur":[300,300,300,300,300,300,300],"transition":[7,7,7,7,7,7,7],"repeat":0,"end":0,"r":1},"on":true,"n":"Audio Reactive Only","ql":"🔈"} ,"16":{"on":true,"bri":32,"transition":7,"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stoLine truncated
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
{"map": {"red_segment": {"x": [389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1143.1535, 1117.1728, 1091.192, 1065.2112, 1039.2305, 1013.2497, 987.269, 961.2882, 935.3074, 909.3267, 883.3459, 857.3651, 831.3844, 805.4036, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535], "y": [1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "back_segment": {"x": [25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 870.0, 840.0, 810.0, 780.0, 750.0, 720.0, 690.0, 660.0, 630.0, 600.0, 570.0, 540.0, 510.0, 480.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "green_segment": {"x": [-805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1143.1535, -1117.1728, -1091.192, -1065.2112, -1039.2305, -1013.2497, -987.269, -961.2882, -935.3074, -909.3267, -883.3459, -857.3651, -831.3844, -805.4036, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.711Line truncated
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
const char JSON_SCREEN_MAP[] = R"(
|
||||
{"map": {"red_segment": {"x": [389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1143.1535, 1117.1728, 1091.192, 1065.2112, 1039.2305, 1013.2497, 987.269, 961.2882, 935.3074, 909.3267, 883.3459, 857.3651, 831.3844, 805.4036, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535], "y": [1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "back_segment": {"x": [25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 870.0, 840.0, 810.0, 780.0, 750.0, 720.0, 690.0, 660.0, 630.0, 600.0, 570.0, 540.0, 510.0, 480.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "green_segment": {"x": [-805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1143.1535, -1117.1728, -1091.192, -1065.2112, -1039.2305, -1013.2497, -987.269, -961.2882, -935.3074, -909.3267, -883.3459, -857.3651, -831.3844, -805.4036, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.711Line truncated
|
||||
)";
|
||||
@@ -0,0 +1,155 @@
|
||||
/// @file Rgb8Video.ino
|
||||
/// @brief Animated rainbows optimized for video display on WS2812 LEDs using
|
||||
/// toVideoRGB_8bit().
|
||||
/// @example Rgb8Video.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"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
UITitle title("ColorBoost");
|
||||
UIDescription description("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);
|
||||
|
||||
// Rgb8Video
|
||||
// Animated, ever-changing rainbows optimized for video display.
|
||||
// Uses CRGB::toVideoRGB_8bit() to boost saturation for better LED display.
|
||||
// Based on Pride2015 by Mark Kriegsman
|
||||
|
||||
#define DATA_PIN 3
|
||||
#define DATA_PIN_2 4 // Second strip for original RGB comparison
|
||||
// #define CLK_PIN 4
|
||||
#define LED_TYPE WS2811
|
||||
#define COLOR_ORDER GRB
|
||||
#define NUM_LEDS_PER_STRIP 16
|
||||
#define NUM_STRIPS 16 // Changed from 2 to 100 for 100x100 matrix
|
||||
#define TOTAL_LEDS (NUM_LEDS_PER_STRIP * NUM_STRIPS) // 100x100 = 10,000 LEDs
|
||||
#define BRIGHTNESS 255
|
||||
|
||||
// Combined LED array for both strips
|
||||
CRGB leds[TOTAL_LEDS];
|
||||
|
||||
// fl::ScreenMap screenmap(lut.data(), lut.size());
|
||||
// fl::ScreenMap screenmap(TOTAL_LEDS);
|
||||
fl::XYMap xyMap =
|
||||
fl::XYMap::constructRectangularGrid(NUM_LEDS_PER_STRIP, NUM_STRIPS);
|
||||
|
||||
void setup() {
|
||||
|
||||
// tell FastLED about the LED strip configuration
|
||||
// First strip (original RGB) - indices 0-199
|
||||
|
||||
FastLED.addLeds<LED_TYPE, DATA_PIN_2, COLOR_ORDER>(leds, TOTAL_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 optimized for video display
|
||||
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 100x100 matrix
|
||||
for (uint16_t y = 0; y < NUM_STRIPS; y++) {
|
||||
for (uint16_t x = 0; x < NUM_LEDS_PER_STRIP; 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 / NUM_LEDS_PER_STRIP);
|
||||
|
||||
// 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);
|
||||
|
||||
// Upper half (rows 0-49): original colors
|
||||
// Lower half (rows 50-99): transformed colors using
|
||||
// toVideoRGB_8bit()
|
||||
if (y > NUM_STRIPS / 3 * 2) {
|
||||
// Upper half - original colors
|
||||
leds[xyMap(x, y)] = original_color;
|
||||
} else if (y > NUM_STRIPS / 3) {
|
||||
// Middle half - transformed colors
|
||||
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 half - transformed colors
|
||||
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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
+89
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#include "fl/has_define.h"
|
||||
|
||||
|
||||
// Platform must be esp32.
|
||||
#if !defined(ESP32) || !__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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+167
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
+101
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
BIN
Binary file not shown.
@@ -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"
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
{"map": {"strip1": {"x": [-0.5, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -1.91, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -21.01, -21.71, -21.01, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 10.61, 9.9, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -10.61, 0.5, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 1.91, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 19.59, 20.3, 19.59, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.2Line truncated
|
||||
Binary file not shown.
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,403 @@
|
||||
|
||||
/*
|
||||
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)
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
@@ -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();
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
+259
@@ -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__
|
||||
+179
@@ -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();
|
||||
}
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LED_REPE_TCL_H_
|
||||
#define LED_REPE_TCL_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "../shared/color.h"
|
||||
#include "../shared/framebuffer.h"
|
||||
#include "../shared/led_rope_interface.h"
|
||||
|
||||
#include "fl/vector.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/screenmap.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
// LedRopeTCL is a C++ wrapper around the Total Control Lighting LED rope
|
||||
// device driver (TCL.h). This wrapper includes automatic setup of the LED
|
||||
// rope and allows the user to use a graphics-state like interface for
|
||||
// talking to the rope. A copy of the rope led state is held in this class
|
||||
// which makes blending operations easier. After all changes by the user
|
||||
// are applied to the rope, the hardware is updated via an explicit Draw()
|
||||
// command.
|
||||
//
|
||||
// Whole-rope blink Example:
|
||||
// #include <SPI.h>
|
||||
// #include <TCL.h> // From CoolNeon (https://bitbucket.org/devries/arduino-tcl)
|
||||
// #include "LedRopeTCL.h"
|
||||
// LedRopeTCL led_rope(100); // 100 led-strand.
|
||||
//
|
||||
// void setup() {} // No setup necessary for Led rope.
|
||||
// void loop() {
|
||||
// led_rope.FillColor(LedRopeTCL::Color3i::Black());
|
||||
// led_rope.Draw();
|
||||
// delay(1000);
|
||||
// led_rope.FillColor(LedRopeTCL::Color3i::White());
|
||||
// led_rope.Draw();
|
||||
// delay(1000);
|
||||
// }
|
||||
|
||||
|
||||
class LedRopeTCL : public LedRopeInterface {
|
||||
public:
|
||||
LedRopeTCL(int n_pixels);
|
||||
virtual ~LedRopeTCL();
|
||||
|
||||
void Draw();
|
||||
void DrawSequentialRepeat(int repeat);
|
||||
void DrawRepeat(const int* value_array, int array_length);
|
||||
void set_draw_offset(int val);
|
||||
|
||||
virtual void Set(int i, const Color3i& c) {
|
||||
frame_buffer_.Set(i, c);
|
||||
}
|
||||
|
||||
Color3i* GetIterator(int i) {
|
||||
return frame_buffer_.GetIterator(i);
|
||||
}
|
||||
|
||||
int length() const { return frame_buffer_.length(); }
|
||||
|
||||
void RawBeginDraw();
|
||||
void RawDrawPixel(const Color3i& c);
|
||||
void RawDrawPixels(const Color3i& c, int n);
|
||||
void RawDrawPixel(uint8_t r, uint8_t g, uint8_t b);
|
||||
void RawCommitDraw();
|
||||
|
||||
protected:
|
||||
void PreDrawSetup();
|
||||
int draw_offset_ = 0;
|
||||
bool lazy_initialized_;
|
||||
FrameBuffer frame_buffer_;
|
||||
bool controller_added_ = false;
|
||||
fl::HeapVector<CRGB> led_buffer_;
|
||||
fl::ScreenMap mScreenMap;
|
||||
};
|
||||
|
||||
#endif // LED_REPE_TCL_H_
|
||||
@@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* @file MIDI.cpp
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino
|
||||
* @author Francois Best
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "MIDI.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
/*! \brief Encode System Exclusive messages.
|
||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
||||
127 without breaking the MIDI protocol. Use this static method to convert the
|
||||
data you want to send.
|
||||
\param inData The data to encode.
|
||||
\param outSysEx The output buffer where to store the encoded message.
|
||||
\param inLength The length of the input buffer.
|
||||
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
|
||||
\return The length of the encoded output buffer.
|
||||
@see decodeSysEx
|
||||
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
|
||||
*/
|
||||
unsigned encodeSysEx(const byte* inData,
|
||||
byte* outSysEx,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits)
|
||||
{
|
||||
unsigned outLength = 0; // Num bytes in output array.
|
||||
byte count = 0; // Num 7bytes in a block.
|
||||
outSysEx[0] = 0;
|
||||
|
||||
for (unsigned i = 0; i < inLength; ++i)
|
||||
{
|
||||
const byte data = inData[i];
|
||||
const byte msb = data >> 7;
|
||||
const byte body = data & 0x7f;
|
||||
|
||||
outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count)));
|
||||
outSysEx[1 + count] = body;
|
||||
|
||||
if (count++ == 6)
|
||||
{
|
||||
outSysEx += 8;
|
||||
outLength += 8;
|
||||
outSysEx[0] = 0;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
return outLength + count + (count != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/*! \brief Decode System Exclusive messages.
|
||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
||||
127 without breaking the MIDI protocol. Use this static method to reassemble
|
||||
your received message.
|
||||
\param inSysEx The SysEx data received from MIDI in.
|
||||
\param outData The output buffer where to store the decrypted message.
|
||||
\param inLength The length of the input buffer.
|
||||
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
|
||||
\return The length of the output buffer.
|
||||
@see encodeSysEx @see getSysExArrayLength
|
||||
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
|
||||
*/
|
||||
unsigned decodeSysEx(const byte* inSysEx,
|
||||
byte* outData,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits)
|
||||
{
|
||||
unsigned count = 0;
|
||||
byte msbStorage = 0;
|
||||
byte byteIndex = 0;
|
||||
|
||||
for (unsigned i = 0; i < inLength; ++i)
|
||||
{
|
||||
if ((i % 8) == 0)
|
||||
{
|
||||
msbStorage = inSysEx[i];
|
||||
byteIndex = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
const byte body = inSysEx[i];
|
||||
const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
|
||||
const byte msb = byte(((msbStorage >> shift) & 1) << 7);
|
||||
byteIndex--;
|
||||
outData[count++] = msb | body;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
Loaded 100 of 3403 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user