diff --git a/libraries/FastLED/README.md b/libraries/FastLED/README.md index 8eacad6..c7d10c1 100644 --- a/libraries/FastLED/README.md +++ b/libraries/FastLED/README.md @@ -8,6 +8,7 @@ FastLED Library [![Documentation](https://img.shields.io/badge/Docs-Doxygen-blue.svg)](http://fastled.io/docs) [![Reddit](https://img.shields.io/badge/reddit-/r/FastLED-orange.svg?logo=reddit)](https://www.reddit.com/r/FastLED/) + Want to control a strip of leds? Or control 10's of thousands? FastLED has your back. FastLED is a robust and massively parallel-led driver for Arduino, Esp32, RaspberryPi, Atmega, Teensy, Uno, Apollo3 Arm and more. Also runs on dirt cheap sub $1 devices, due to it's incredibly small compile size. High end devices can drive upto ~30k LEDS (Teensy) and ~20k on ESP32. Supports nearly every single LED chipset in existence. Background rendering (ESP32/Teensy/RaspberriPi) means you can respond to user input while the leds render. FastLED is the third [most popular library on Arduino](https://docs.arduino.cc/libraries/). @@ -75,7 +76,11 @@ For more examples, see this [link](examples). Web compiled [examples](https://za Video: -https://github.com/user-attachments/assets/9155124b-a93e-4317-b272-8bacc1b9c3a8 + + +https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af + + #### Major release for tech-artists! @@ -248,6 +253,10 @@ Update: max overclock has been reported at +70%: https://www.reddit.com/r/FastLE [![esp32h2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml) + +[![esp32p4](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml) + + *Specific features* [![esp32_i2s_ws2812](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml) diff --git a/libraries/FastLED/RELEASE.md b/libraries/FastLED/RELEASE.md index 5bb022b..9d3597c 100644 --- a/libraries/FastLED/RELEASE.md +++ b/libraries/FastLED/RELEASE.md @@ -24,10 +24,10 @@ Release notes should list highlight changes (not necessarily all minor bug fixes Git commands to commit and tag release' ```bash -$ git commit -am "Rev 3.9.20 - Misc fixes" -$ git tag 3.9.20 master +$ git commit -am "Rev 3.10.1 - Bug fix for 3.10.0" +$ git tag 3.10.1 master $ git push -$ git push origin 3.9.20 +$ git push origin 3.10.1 ``` Then use the GitHub UI to make a new “Release”: diff --git a/libraries/FastLED/ci/ci-compile.py b/libraries/FastLED/ci/ci-compile.py index fd0f9ab..f5a6cf0 100644 --- a/libraries/FastLED/ci/ci-compile.py +++ b/libraries/FastLED/ci/ci-compile.py @@ -16,7 +16,7 @@ from ci.locked_print import locked_print HERE = Path(__file__).parent.resolve() -LIBS = ["src", "ci"] +LIBS = ["src"] EXTRA_LIBS = [ "https://github.com/me-no-dev/ESPAsyncWebServer.git", "ArduinoOTA", @@ -45,6 +45,7 @@ DEFAULT_BOARDS_NAMES = [ "ATtiny1616", "esp32c6", "esp32s3", + "esp32p4", "yun", "digix", "teensy30", @@ -69,16 +70,22 @@ OTHER_BOARDS_NAMES = [ # Examples to compile. DEFAULT_EXAMPLES = [ + "Animartrix", "Apa102", "Apa102HD", "Apa102HDOverride", + "Audio", "Blink", "Blur", "Chromancer", "ColorPalette", "ColorTemperature", + "Corkscrew", + "CompileTest", "Cylon", "DemoReel100", + "Downscale", + "FestivalStick", "FirstLight", "Fire2012", "Multiple/MultipleStripsInOneArray", @@ -96,6 +103,8 @@ DEFAULT_EXAMPLES = [ "RGBWEmulated", "TwinkleFox", "XYMatrix", + "FireMatrix", + "FireCylinder", "FxGfx2Video", "FxSdCard", "FxCylon", diff --git a/libraries/FastLED/ci/ci/boards.py b/libraries/FastLED/ci/ci/boards.py index ba844f5..756b4de 100644 --- a/libraries/FastLED/ci/ci/boards.py +++ b/libraries/FastLED/ci/ci/boards.py @@ -11,6 +11,7 @@ ESP32_IDF_5_1_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/r # TODO: Upgrade toolkit to 5.3 ESP32_IDF_5_3_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip" +ESP32_IDF_5_4_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip" ESP32_IDF_5_1_PIOARDUINO_LATEST = ( "https://github.com/pioarduino/platform-espressif32.git#develop" ) @@ -22,6 +23,8 @@ APOLLO3_2_2_0 = "https://github.com/nigelb/platform-apollo3blue" # Old fork that we were using # ESP32_IDF_5_1_PIOARDUINO = "https://github.com/zackees/platform-espressif32#Arduino/IDF5" +# ALL will be auto populated in the Board constructor whenever a +# board is defined. ALL: list["Board"] = [] @@ -156,7 +159,7 @@ ESP32_C2_DEVKITM_1 = Board( board_name="esp32c2", real_board_name="esp32-c2-devkitm-1", use_pio_run=True, - platform="https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5", + platform="https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip", defines=["CONFIG_IDF_TARGET_ESP32C2=1"], ) @@ -175,14 +178,7 @@ ESP32_C6_DEVKITC_1 = Board( ESP32_S3_DEVKITC_1 = Board( board_name="esp32s3", real_board_name="seeed_xiao_esp32s3", # Seeed Xiao ESP32-S3 has psram. - platform=ESP32_IDF_5_3_PIOARDUINO, - defines=[ - "BOARD_HAS_PSRAM", - ], - build_flags=[ # Reserved for future use. - "-mfix-esp32-psram-cache-issue", - "-mfix-esp32-psram-cache-strategy=memw", - ], + platform=ESP32_IDF_5_4_PIOARDUINO, board_partitions="huge_app.csv", # Reserved for future use. ) @@ -199,6 +195,13 @@ ESP32_H2_DEVKITM_1 = Board( platform=ESP32_IDF_5_3_PIOARDUINO, ) +ESP32_P4 = Board( + board_name="esp32p4", + real_board_name="esp32-p4-evboard", + platform_needs_install=True, # Install platform package to get the boards + platform="https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip", +) + ADA_FEATHER_NRF52840_SENSE = Board( board_name="adafruit_feather_nrf52840_sense", platform="nordicnrf52", diff --git a/libraries/FastLED/ci/ci/check_files.py b/libraries/FastLED/ci/ci/check_files.py new file mode 100644 index 0000000..8ae8c98 --- /dev/null +++ b/libraries/FastLED/ci/ci/check_files.py @@ -0,0 +1,215 @@ +import os +from abc import ABC, abstractmethod +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass +from typing import Dict, List, Optional + +from ci.paths import PROJECT_ROOT + +SRC_ROOT = PROJECT_ROOT / "src" + +NUM_WORKERS = (os.cpu_count() or 1) * 4 + +EXCLUDED_FILES = [ + "stub_main.cpp", +] + + +@dataclass +class FileContent: + """Container for file content and metadata.""" + + path: str + content: str + lines: List[str] + + def __post_init__(self): + if not self.lines: + self.lines = self.content.splitlines() + + +class FileContentChecker(ABC): + """Abstract base class for checking file content.""" + + @abstractmethod + def should_process_file(self, file_path: str) -> bool: + """Predicate to determine if a file should be processed. + + Args: + file_path: Path to the file to check + + Returns: + True if the file should be processed, False otherwise + """ + pass + + @abstractmethod + def check_file_content(self, file_content: FileContent) -> List[str]: + """Check the file content and return any issues found. + + Args: + file_content: FileContent object containing path, content, and lines + + Returns: + List of error messages, empty if no issues found + """ + pass + + +class MultiCheckerFileProcessor: + """Processor that can run multiple checkers on files.""" + + def __init__(self): + pass + + def process_files_with_checkers( + self, file_paths: List[str], checkers: List[FileContentChecker] + ) -> Dict[str, List[str]]: + """Process files with multiple checkers. + + Args: + file_paths: List of file paths to process + checkers: List of checker instances to run on the files + + Returns: + Dictionary mapping checker class name to list of issues found + """ + # Initialize results dictionary for each checker + results = {} + for checker in checkers: + checker_name = checker.__class__.__name__ + results[checker_name] = [] + + # Process each file + for file_path in file_paths: + # Check if any checker wants to process this file + interested_checkers = [ + checker + for checker in checkers + if checker.should_process_file(file_path) + ] + + # If any checker is interested, read the file once + if interested_checkers: + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + # Create FileContent object with lines split + file_content = FileContent( + path=file_path, content=content, lines=content.splitlines() + ) + + # Pass the file content to all interested checkers + for checker in interested_checkers: + checker_name = checker.__class__.__name__ + issues = checker.check_file_content(file_content) + results[checker_name].extend(issues) + + except Exception as e: + # Add error to all interested checkers + error_msg = f"Error reading file {file_path}: {str(e)}" + for checker in interested_checkers: + checker_name = checker.__class__.__name__ + results[checker_name].append(error_msg) + + return results + + +# Legacy compatibility classes +class FileProcessorCallback(FileContentChecker): + """Legacy compatibility wrapper - delegates to FileContentChecker methods.""" + + def check_file_content_legacy(self, file_path: str, content: str) -> List[str]: + """Legacy method signature for backward compatibility.""" + file_content = FileContent(path=file_path, content=content, lines=[]) + return self.check_file_content(file_content) + + +class GenericFileSearcher: + """Generic file searcher that processes files using a callback pattern.""" + + def __init__(self, max_workers: Optional[int] = None): + self.max_workers = max_workers or NUM_WORKERS + + def search_directory( + self, start_dir: str, callback: FileProcessorCallback + ) -> List[str]: + """Search a directory and process files using the provided callback. + + Args: + start_dir: Directory to start searching from + callback: Callback class to handle file processing + + Returns: + List of all issues found across all files + """ + files_to_check = [] + + # Collect all files that should be processed + for root, _, files in os.walk(start_dir): + for file in files: + file_path = os.path.join(root, file) + if callback.should_process_file(file_path): + files_to_check.append(file_path) + + # Process files in parallel + all_issues = [] + with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + futures = [ + executor.submit(self._process_single_file, file_path, callback) + for file_path in files_to_check + ] + for future in futures: + all_issues.extend(future.result()) + + return all_issues + + def _process_single_file( + self, file_path: str, callback: FileProcessorCallback + ) -> List[str]: + """Process a single file using the callback. + + Args: + file_path: Path to the file to process + callback: Callback to use for processing + + Returns: + List of issues found in this file + """ + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + file_content = FileContent(path=file_path, content=content, lines=[]) + return callback.check_file_content(file_content) + except Exception as e: + return [f"Error processing file {file_path}: {str(e)}"] + + +def collect_files_to_check( + test_directories: List[str], extensions: List[str] | None = None +) -> List[str]: + """Collect all files to check from the given directories.""" + if extensions is None: + extensions = [".cpp", ".h", ".hpp"] + + files_to_check = [] + + # Search each directory + for directory in test_directories: + if os.path.exists(directory): + for root, _, files in os.walk(directory): + for file in files: + if any(file.endswith(ext) for ext in extensions): + file_path = os.path.join(root, file) + files_to_check.append(file_path) + + # Also check the main src directory files (not subdirectories) + for file in os.listdir(SRC_ROOT): + file_path = os.path.join(SRC_ROOT, file) + if os.path.isfile(file_path) and any( + file_path.endswith(ext) for ext in extensions + ): + files_to_check.append(file_path) + + return files_to_check diff --git a/libraries/FastLED/ci/optimization_report.py b/libraries/FastLED/ci/optimization_report.py index e9babad..bd90747 100644 --- a/libraries/FastLED/ci/optimization_report.py +++ b/libraries/FastLED/ci/optimization_report.py @@ -33,7 +33,7 @@ def main() -> int: board_dir = board_dirs[which] # build_info_json = board_dir / "build_info.json" optimization_report = board_dir / "optimization_report.txt" - text = optimization_report.read_text(encoding="utf-8") + text = optimization_report.read_text(encoding="utf-8", errors="replace") print(text) return 0 diff --git a/libraries/FastLED/ci/tests/test_no_banned_headers.py b/libraries/FastLED/ci/tests/test_no_banned_headers.py index 80471e1..8d182f2 100644 --- a/libraries/FastLED/ci/tests/test_no_banned_headers.py +++ b/libraries/FastLED/ci/tests/test_no_banned_headers.py @@ -1,14 +1,17 @@ import os import unittest -from concurrent.futures import ThreadPoolExecutor +from typing import Callable, List +from ci.check_files import ( + EXCLUDED_FILES, + FileContent, + FileContentChecker, + MultiCheckerFileProcessor, + collect_files_to_check, +) from ci.paths import PROJECT_ROOT SRC_ROOT = PROJECT_ROOT / "src" -PLATFORMS_DIR = os.path.join(SRC_ROOT, "platforms") -PLATFORMS_ESP_DIR = os.path.join(PLATFORMS_DIR, "esp") - -NUM_WORKERS = (os.cpu_count() or 1) * 4 ENABLE_PARANOID_GNU_HEADER_INSPECTION = False @@ -17,8 +20,7 @@ if ENABLE_PARANOID_GNU_HEADER_INSPECTION: else: BANNED_HEADERS_ESP = [] - -BANNED_HEADERS_CORE = [ +BANNED_HEADERS_COMMON = [ "assert.h", "iostream", "stdio.h", @@ -56,76 +58,123 @@ BANNED_HEADERS_CORE = [ "cstdint", "cstddef", # this certainally fails "type_traits", # this certainally fails - "Arduino.h", -] + BANNED_HEADERS_ESP - -EXCLUDED_FILES = [ - "stub_main.cpp", ] +BANNED_HEADERS_CORE = BANNED_HEADERS_COMMON + BANNED_HEADERS_ESP + ["Arduino.h"] + + +class BannedHeadersChecker(FileContentChecker): + """Checker class for banned headers.""" + + def __init__(self, banned_headers_list: List[str]): + """Initialize with the list of banned headers to check for.""" + self.banned_headers_list = banned_headers_list + + def should_process_file(self, file_path: str) -> bool: + """Check if file should be processed for banned headers.""" + # Check file extension + if not file_path.endswith((".cpp", ".h", ".hpp", ".ino")): + return False + + # Check if file is in excluded list + if any(file_path.endswith(excluded) for excluded in EXCLUDED_FILES): + return False + + return True + + def check_file_content(self, file_content: FileContent) -> List[str]: + """Check file content for banned headers.""" + failings = [] + + if len(self.banned_headers_list) == 0: + return failings + + # Check each line for banned headers + for line_number, line in enumerate(file_content.lines, 1): + if line.strip().startswith("//"): + continue + + for header in self.banned_headers_list: + if ( + f"#include <{header}>" in line or f'#include "{header}"' in line + ) and "// ok include" not in line: + failings.append( + f"Found banned header '{header}' in {file_content.path}:{line_number}" + ) + + return failings + + +def _test_no_banned_headers( + test_directories: list[str], + banned_headers_list: List[str], + on_fail: Callable[[str], None], +) -> None: + """Searches through the program files to check for banned headers.""" + # Collect files to check + files_to_check = collect_files_to_check(test_directories) + + # Create processor and checker + processor = MultiCheckerFileProcessor() + checker = BannedHeadersChecker(banned_headers_list) + + # Process files + results = processor.process_files_with_checkers(files_to_check, [checker]) + + # Get results for banned headers checker + all_failings = results.get("BannedHeadersChecker", []) or [] + + if all_failings: + msg = f"Found {len(all_failings)} banned header(s): \n" + "\n".join( + all_failings + ) + for failing in all_failings: + print(failing) + + on_fail(msg) + else: + print("No banned headers found.") + class TestNoBannedHeaders(unittest.TestCase): - def check_file(self, file_path: str) -> list[str]: - failings: list[str] = [] - banned_headers_list = [] - if file_path.startswith(PLATFORMS_DIR): - # continue # Skip the platforms directory - if file_path.startswith(PLATFORMS_ESP_DIR): - banned_headers_list = BANNED_HEADERS_ESP - else: - return failings - if len(banned_headers_list) == 0: - return failings - with open(file_path, "r", encoding="utf-8") as f: + def test_no_banned_headers_src(self) -> None: + """Searches through the program files to check for banned headers.""" - for line_number, line in enumerate(f, 1): - if line.startswith("//"): - continue - for header in banned_headers_list: - if ( - f"#include <{header}>" in line or f'#include "{header}"' in line - ) and "// ok include" not in line: - failings.append( - f"Found banned header '{header}' in {file_path}:{line_number}" - ) - return failings - - def test_no_banned_headers(self) -> None: - """Searches through the program files to check for banned headers, excluding src/platforms.""" - files_to_check = [] - for root, _, files in os.walk(SRC_ROOT): - for file in files: - if file.endswith( - (".cpp", ".h", ".hpp") - ): # Add or remove file extensions as needed - file_path = os.path.join(root, file) - if not any( - file_path.endswith(excluded) for excluded in EXCLUDED_FILES - ): - files_to_check.append(file_path) - - all_failings = [] - with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor: - futures = [ - executor.submit(self.check_file, file_path) - for file_path in files_to_check - ] - for future in futures: - all_failings.extend(future.result()) - - if all_failings: - msg = f"Found {len(all_failings)} banned header(s): \n" + "\n".join( - all_failings - ) - for failing in all_failings: - print(failing) + def on_fail(msg: str) -> None: self.fail( msg + "\n" "You can add '// ok include' at the end of the line to silence this error for specific inclusions." ) - else: - print("No banned headers found.") + + # Test directories as requested + test_directories = [ + os.path.join(SRC_ROOT, "fl"), + os.path.join(SRC_ROOT, "fx"), + os.path.join(SRC_ROOT, "sensors"), + ] + _test_no_banned_headers( + test_directories=test_directories, + banned_headers_list=BANNED_HEADERS_CORE, + on_fail=on_fail, + ) + + def test_no_banned_headers_examples(self) -> None: + """Searches through the program files to check for banned headers.""" + + def on_fail(msg: str) -> None: + self.fail( + msg + "\n" + "You can add '// ok include' at the end of the line to silence this error for specific inclusions." + ) + + test_directories = ["examples"] + + _test_no_banned_headers( + test_directories=test_directories, + banned_headers_list=BANNED_HEADERS_COMMON, + on_fail=on_fail, + ) if __name__ == "__main__": diff --git a/libraries/FastLED/docs/Doxyfile b/libraries/FastLED/docs/Doxyfile index 4462bab..1764991 100644 --- a/libraries/FastLED/docs/Doxyfile +++ b/libraries/FastLED/docs/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = FastLED # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.9.20 +PROJECT_NUMBER = 3.10.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a diff --git a/libraries/FastLED/examples/FxAnimartrix/FxAnimartrix.ino b/libraries/FastLED/examples/Animartrix/Animartrix.ino similarity index 55% rename from libraries/FastLED/examples/FxAnimartrix/FxAnimartrix.ino rename to libraries/FastLED/examples/Animartrix/Animartrix.ino index f6af9f0..dedd278 100644 --- a/libraries/FastLED/examples/FxAnimartrix/FxAnimartrix.ino +++ b/libraries/FastLED/examples/Animartrix/Animartrix.ino @@ -3,10 +3,31 @@ /// @author Stefan Petrick /// @author Zach Vorhies (FastLED adaptation) /// -/// 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` + +/* +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 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. +*/ + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else #include @@ -34,6 +55,10 @@ using namespace fl; #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 + CRGB leds[NUM_LEDS]; XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT); @@ -44,19 +69,33 @@ UIDescription description("Demo of the Animatrix effects. @author of fx is Stefa UISlider brightness("Brightness", 255, 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); + void setup() { auto screen_map = xyMap.toScreenMap(); - screen_map.setDiameter(.1); + screen_map.setDiameter(LED_DIAMETER); FastLED.addLeds(leds, NUM_LEDS) .setCorrection(TypicalLEDStrip) .setScreenMap(screen_map); FastLED.setBrightness(brightness); 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(value)); + }); } void loop() { @@ -72,3 +111,4 @@ void loop() { } +#endif // __AVR__ diff --git a/libraries/FastLED/examples/Audio/Audio.ino b/libraries/FastLED/examples/Audio/Audio.ino index 29ee035..41cd577 100644 --- a/libraries/FastLED/examples/Audio/Audio.ino +++ b/libraries/FastLED/examples/Audio/Audio.ino @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable @@ -11,6 +16,14 @@ all the UI elements you see below. #include #include +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + + + #include "fl/audio.h" #include "fl/downscale.h" #include "fl/draw_visitor.h" @@ -21,9 +34,11 @@ all the UI elements you see below. #include "fl/time_alpha.h" #include "fl/ui.h" #include "fl/xypath.h" -#include "fx.h" +#include "fl/unused.h" +#include "fx_audio.h" #include "fx/time.h" + // Sketch. #include "fl/function.h" @@ -34,6 +49,7 @@ using namespace fl; #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."); @@ -106,7 +122,7 @@ void setup() { audioFadeTracker.setOutputTime(value); FASTLED_WARN("Output time seconds: " << value); }); - FastLED.addLeds(leds, ledsXY.getTotal()) + FastLED.addLeds(leds, ledsXY.getTotal()) .setScreenMap(screenmap); } @@ -144,10 +160,6 @@ void loop() { if (triggered) { FASTLED_WARN("Triggered"); } - // fl::clear(framebuffer); - // fl::clear(framebuffer); - - static uint32_t frame = 0; // x = pointX.as_int(); y = HEIGHT / 2; @@ -164,6 +176,7 @@ void loop() { soundLevelMeter.processBlock(sample.pcm()); // FASTLED_WARN("") auto dbfs = soundLevelMeter.getDBFS(); + FASTLED_UNUSED(dbfs); // FASTLED_WARN("getDBFS: " << dbfs); int32_t max = 0; for (int i = 0; i < sample.pcm().size(); ++i) { @@ -187,6 +200,7 @@ void loop() { if (enableFFT) { auto max_x = fftOut.bins_raw.size() - 1; + FASTLED_UNUSED(max_x); for (int i = 0; i < fftOut.bins_raw.size(); ++i) { auto x = i; auto v = fftOut.bins_db[i]; @@ -232,3 +246,5 @@ void loop() { FastLED.show(); } + +#endif // __AVR__ diff --git a/libraries/FastLED/examples/Audio/fx.h b/libraries/FastLED/examples/Audio/fx_audio.h similarity index 84% rename from libraries/FastLED/examples/Audio/fx.h rename to libraries/FastLED/examples/Audio/fx_audio.h index eadd509..c2c34e5 100644 --- a/libraries/FastLED/examples/Audio/fx.h +++ b/libraries/FastLED/examples/Audio/fx_audio.h @@ -1,9 +1,7 @@ -#include -#include -#include -#include + #include "fl/time_alpha.h" +#include "fl/math_macros.h" /// Tracks a smoothed peak with attack, decay, and output-inertia time-constants. class MaxFadeTracker { @@ -34,8 +32,8 @@ public: // 1) block peak float peak = 0.0f; for (size_t i = 0; i < length; ++i) { - float v = std::abs(samples[i]) * (1.0f/32768.0f); - peak = std::max(peak, v); + float v = ABS(samples[i]) * (1.0f/32768.0f); + peak = MAX(peak, v); } // 2) time delta @@ -43,15 +41,15 @@ public: // 3) update currentLevel_ with attack/decay if (peak > currentLevel_) { - float riseFactor = 1.0f - std::exp(-attackRate_ * dt); + float riseFactor = 1.0f - exp(-attackRate_ * dt); currentLevel_ += (peak - currentLevel_) * riseFactor; } else { - float decayFactor = std::exp(-decayRate_ * dt); + float decayFactor = exp(-decayRate_ * dt); currentLevel_ *= decayFactor; } // 4) output inertia: smooth smoothedOutput_ → currentLevel_ - float outFactor = 1.0f - std::exp(-outputRate_ * dt); + float outFactor = 1.0f - exp(-outputRate_ * dt); smoothedOutput_ += (currentLevel_ - smoothedOutput_) * outFactor; return smoothedOutput_; diff --git a/libraries/FastLED/examples/Chromancer/Chromancer.ino b/libraries/FastLED/examples/Chromancer/Chromancer.ino index 539fa86..49cd69d 100644 --- a/libraries/FastLED/examples/Chromancer/Chromancer.ino +++ b/libraries/FastLED/examples/Chromancer/Chromancer.ino @@ -11,7 +11,7 @@ */ #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) -// Avr is not powerful enough. +// Platform does not have enough memory // Other platforms have weird issues. Will revisit this later. void setup() {} void loop() {} diff --git a/libraries/FastLED/examples/CompileTest/CompileTest.ino b/libraries/FastLED/examples/CompileTest/CompileTest.ino new file mode 100644 index 0000000..4006422 --- /dev/null +++ b/libraries/FastLED/examples/CompileTest/CompileTest.ino @@ -0,0 +1,18 @@ +#include "FastLED.h" + +#if defined(__AVR__) +#include "avr_test.h" +#elif defined(ESP32) +#include "esp_test.h" +#endif + + +void setup() { + Serial.begin(115200); + Serial.println("Setup"); +} + +void loop() { + Serial.println("Loop"); + delay(100); +} \ No newline at end of file diff --git a/libraries/FastLED/examples/CompileTest/avr_test.h b/libraries/FastLED/examples/CompileTest/avr_test.h new file mode 100644 index 0000000..8e5aa3e --- /dev/null +++ b/libraries/FastLED/examples/CompileTest/avr_test.h @@ -0,0 +1,13 @@ +#pragma once + +#include "FastLED.h" + +void avr_tests() { +#if FASTLED_USE_PROGMEM != 1 +#error "FASTLED_USE_PROGMEM is not 1 for AVR" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY != 0 +#error "SKETCH_HAS_LOTS_OF_MEMORY is not 0 for AVR" +#endif +} \ No newline at end of file diff --git a/libraries/FastLED/examples/CompileTest/esp_8266_test.h b/libraries/FastLED/examples/CompileTest/esp_8266_test.h new file mode 100644 index 0000000..765235e --- /dev/null +++ b/libraries/FastLED/examples/CompileTest/esp_8266_test.h @@ -0,0 +1,13 @@ +#pragma once + +#include "FastLED.h" + +void esp_8266_tests() { +#if FASTLED_USE_PROGMEM != 1 +#error "FASTLED_USE_PROGMEM is not 1 for AVR" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY != 0 +#error "SKETCH_HAS_LOTS_OF_MEMORY is not 0 for AVR" +#endif +} \ No newline at end of file diff --git a/libraries/FastLED/examples/CompileTest/esp_s3_test.h b/libraries/FastLED/examples/CompileTest/esp_s3_test.h new file mode 100644 index 0000000..6a3af3b --- /dev/null +++ b/libraries/FastLED/examples/CompileTest/esp_s3_test.h @@ -0,0 +1,13 @@ +#pragma once + +#include "FastLED.h" + +void esp32_s3_tests() { +#if FASTLED_USE_PROGMEM != 0 +#error "FASTLED_USE_PROGMEM prog memory is turned on for esap32-s3" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY == 0 +#error "SKETCH_HAS_LOTS_OF_MEMORY should have lots of memory for esp32-s3" +#endif +} \ No newline at end of file diff --git a/libraries/FastLED/examples/CompileTest/esp_test.h b/libraries/FastLED/examples/CompileTest/esp_test.h new file mode 100644 index 0000000..b976c28 --- /dev/null +++ b/libraries/FastLED/examples/CompileTest/esp_test.h @@ -0,0 +1,10 @@ + +#pragma once + +#include "sdkconfig.h" + +#if defined(ESP8266) +#include "esp_8266_test.h" +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +#include "esp_s3_test.h" +#endif \ No newline at end of file diff --git a/libraries/FastLED/examples/Corkscrew/Corkscrew.h b/libraries/FastLED/examples/Corkscrew/Corkscrew.h new file mode 100644 index 0000000..04298cf --- /dev/null +++ b/libraries/FastLED/examples/Corkscrew/Corkscrew.h @@ -0,0 +1,134 @@ +/* +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. + +*/ + +#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 +// #include "vec3.h" + +using namespace fl; + +#define PIN_DATA 9 + +#define NUM_LEDS 288 +#define CORKSCREW_TOTAL_LENGTH 100 +#define CORKSCREW_TOTAL_HEIGHT 23.25 // when height = 0, it's a circle. + // wrapped up over 19 turns +#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); + +// CRGB leds[NUM_LEDS]; + +// Tested on a 288 led (2x 144 max density led strip) with 19 turns +// with 23.25cm height, 19 turns, and ~15.5 LEDs per turn. +Corkscrew::Input corkscrewInput(CORKSCREW_TOTAL_LENGTH, CORKSCREW_TOTAL_HEIGHT, + CORKSCREW_TURNS, // Default to 19 turns + NUM_LEDS, // Default to dense 144 leds. + 0 // offset to account for gaps between segments +); + +// Corkscrew::State corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput); +Corkscrew corkscrew(corkscrewInput); + +// 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 mapCorkScrew = makeCorkScrew(args); +fl::ScreenMap screenMap; +fl::Grid frameBuffer; + +void setup() { + int width = corkscrew.cylinder_width(); + int height = corkscrew.cylinder_height(); + + 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(leds, num_leds); + + fl::ScreenMap screenMap = xyMap.toScreenMap(); + screenMap.setDiameter(.2f); + + // Set the screen map for the controller + controller->setScreenMap(screenMap); +} + +void loop() { + fl::clear(frameBuffer); + static float pos = 0; + pos += speed.value(); + if (pos > corkscrew.size() - 1) { + pos = 0; // Reset to the beginning + } + + 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); + vec2i16 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_exact(pos); + vec2i16 pos_i16 = vec2i16(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(); +} diff --git a/libraries/FastLED/examples/Corkscrew/Corkscrew.ino b/libraries/FastLED/examples/Corkscrew/Corkscrew.ino new file mode 100644 index 0000000..d169a41 --- /dev/null +++ b/libraries/FastLED/examples/Corkscrew/Corkscrew.ino @@ -0,0 +1,11 @@ +#include // 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 diff --git a/libraries/FastLED/examples/BilinearCompression/BilinearCompression.ino b/libraries/FastLED/examples/Downscale/Downscale.h similarity index 91% rename from libraries/FastLED/examples/BilinearCompression/BilinearCompression.ino rename to libraries/FastLED/examples/Downscale/Downscale.h index 7f011a6..c947773 100644 --- a/libraries/FastLED/examples/BilinearCompression/BilinearCompression.ino +++ b/libraries/FastLED/examples/Downscale/Downscale.h @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable @@ -11,6 +16,7 @@ all the UI elements you see below. #include #include +#include "fl/downscale.h" #include "fl/draw_visitor.h" #include "fl/math_macros.h" #include "fl/raster.h" @@ -18,7 +24,6 @@ all the UI elements you see below. #include "fl/ui.h" #include "fl/xypath.h" #include "fx/time.h" -#include "fl/bilinear_compression.h" // Sketch. #include "src/wave.h" @@ -32,10 +37,11 @@ using namespace fl; #define TIME_ANIMATION 1000 // ms CRGB leds[NUM_LEDS]; -CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer +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. +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 @@ -44,7 +50,6 @@ XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2, false); // Framebuffer is regular rectang WaveEffect wave_fx; // init in setup(). fl::vector shapes = CreateXYPaths(WIDTH, HEIGHT); - XYRaster raster(WIDTH, HEIGHT); TimeWarp time_warp; @@ -77,12 +82,12 @@ void setupUiCallbacks() { maxAnimation.onChanged( [](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); }); - trigger.onChanged([]() { + trigger.onClicked([]() { // shapeProgress.trigger(millis()); FASTLED_WARN("Trigger pressed"); }); - useWaveFx.onChanged([](bool on) { - if (on) { + useWaveFx.onChanged([](fl::UICheckbox &checkbox) { + if (checkbox.value()) { FASTLED_WARN("WaveFX enabled"); } else { FASTLED_WARN("WaveFX disabled"); @@ -94,7 +99,8 @@ void setup() { Serial.begin(115200); auto screenmap = xyMap.toScreenMap(); screenmap.setDiameter(.2); - FastLED.addLeds(leds, xyMap.getTotal()).setScreenMap(screenmap); + FastLED.addLeds(leds, xyMap.getTotal()) + .setScreenMap(screenmap); auto screenmap2 = xyMap_Dst.toScreenMap(); screenmap.setDiameter(.5); screenmap2.addOffsetY(-HEIGHT / 2); @@ -142,9 +148,6 @@ void loop() { s_prev_alpha = curr_alpha; } - const bool is_active = - true || curr_alpha < maxAnimation.value() && curr_alpha > 0.0f; - static uint32_t frame = 0; frame++; clearLeds(); @@ -171,9 +174,6 @@ void loop() { } uint8_t alpha = fl::map_range(i, 0.0f, number_of_steps - 1, 64, 255); - if (!is_active) { - alpha = 0; - } Tile2x2_u8 subpixel = shape->at_subpixel(a); subpixel.scale(alpha); // subpixels.push_back(subpixel); @@ -182,8 +182,7 @@ void loop() { s_prev_alpha = curr_alpha; - - if (useWaveFx && is_active) { + if (useWaveFx) { DrawRasterToWaveSimulator draw_wave_fx(&wave_fx); raster.draw(xyMap, draw_wave_fx); } else { diff --git a/libraries/FastLED/examples/Downscale/Downscale.ino b/libraries/FastLED/examples/Downscale/Downscale.ino new file mode 100644 index 0000000..147ff32 --- /dev/null +++ b/libraries/FastLED/examples/Downscale/Downscale.ino @@ -0,0 +1,14 @@ + +#include +#include + + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + +#include "Downscale.h" + +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/libraries/FastLED/examples/BilinearCompression/src/wave.cpp b/libraries/FastLED/examples/Downscale/src/wave.cpp similarity index 100% rename from libraries/FastLED/examples/BilinearCompression/src/wave.cpp rename to libraries/FastLED/examples/Downscale/src/wave.cpp diff --git a/libraries/FastLED/examples/BilinearCompression/src/wave.h b/libraries/FastLED/examples/Downscale/src/wave.h similarity index 91% rename from libraries/FastLED/examples/BilinearCompression/src/wave.h rename to libraries/FastLED/examples/Downscale/src/wave.h index 6729df5..1401139 100644 --- a/libraries/FastLED/examples/BilinearCompression/src/wave.h +++ b/libraries/FastLED/examples/Downscale/src/wave.h @@ -22,7 +22,7 @@ struct WaveEffect { struct DrawRasterToWaveSimulator { DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {} - void draw(const vec2 &pt, uint32_t index, uint8_t value) { + void draw(const vec2 &pt, uint32_t index, uint8_t value) { float valuef = value / 255.0f; int xx = pt.x; int yy = pt.y; diff --git a/libraries/FastLED/examples/BilinearCompression/src/xypaths.cpp b/libraries/FastLED/examples/Downscale/src/xypaths.cpp similarity index 100% rename from libraries/FastLED/examples/BilinearCompression/src/xypaths.cpp rename to libraries/FastLED/examples/Downscale/src/xypaths.cpp diff --git a/libraries/FastLED/examples/BilinearCompression/src/xypaths.h b/libraries/FastLED/examples/Downscale/src/xypaths.h similarity index 100% rename from libraries/FastLED/examples/BilinearCompression/src/xypaths.h rename to libraries/FastLED/examples/Downscale/src/xypaths.h diff --git a/libraries/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino b/libraries/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino index 146181c..977fc85 100644 --- a/libraries/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino +++ b/libraries/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino @@ -3,60 +3,40 @@ /// 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. -/// - You probably aren't going to be able to use this in ArduinoIDE, because ArduinoIDE does not allow you to put in the necessary build flags. -/// You will need to use PlatformIO to build this. -/// - These flags enable PSRAM. /// - 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. +/// - 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 works, remove the printfs and see if that fixes the problem. -/// - You MUST use all the available PINS specified in this demo. Anything less than that will cause FastLED to crash. -/// - Certain leds will turn white in debug mode. Probably has something to do with timing. +/// 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? No. +/// 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? Kinda. We put in a hack to add the extra wait time of 300 uS. +/// What about the new WS2812-5VB leds? Yes, they have 250us timing. /// -/// What are the advantages of using the FastLED bindings over the raw driver? -/// - FastLED api is more user friendly since you don't have to combine all your leds into one rectangular block. -/// - FastLED api allows you to have different sized strips which will be upscaled to the largest strip internally. -/// -/// What are the advantages of using the raw driver over the FastLED bindings? -/// - The raw driver uses less memory because it doesn't have a frame buffer copy. +/// 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/51.03.04/platform-espressif32.zip +// platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip // framework = arduino // board = seeed_xiao_esp32s3 -// build_flags = -// ${env:generic-esp.build_flags} -// -DBOARD_HAS_PSRAM -// -mfix-esp32-psram-cache-issue -// -mfix-esp32-psram-cache-strategy=memw -// board_build.partitions = huge_app.csv -// -// Then in your setup function you are going to want to call psramInit(); -// -// Want to get a contributor badge for FastLED? This driver has only been lightly tested. -// There are certain open questions: -// - Can the pins order for the strips be changed? (the pins can be defined arbitrarily,Tested on esp32s3, esp32duino version 3.2.0) -// - Are there some combination of pins that can be ommitted? -// - What other caveats are there? -// -// If you know the answer to these questions then please submit a PR to the FastLED repo and -// we will update the information for the community. -#include -#define FASTLED_USES_ESP32S3_I2S +#define FASTLED_USES_ESP32S3_I2S // Must define this before including FastLED.h #include "FastLED.h" #include "fl/assert.h" @@ -66,6 +46,8 @@ #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 @@ -84,8 +66,9 @@ #define EXAMPLE_PIN_NUM_DATA15 18 // R4 -const bool gUseFastLEDApi = true; // Set this to false to use the raw driver. - +// 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, @@ -105,10 +88,9 @@ int PINS[] = { EXAMPLE_PIN_NUM_DATA15 }; -fl::InternalI2SDriver *driver = nullptr; CRGB leds[NUM_LEDS]; -void setup_i2s_using_fastled_api() { +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( @@ -159,15 +141,13 @@ void setup_i2s_using_fastled_api() { FastLED.addLeds( leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP ); - FastLED.setBrightness(32); } void setup() { - psramInit(); // IMPORTANT: This is required to enable PSRAM. If you don't do this, the driver will not work. // put your setup code here, to run once: - Serial.begin(115200); + 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()); @@ -175,15 +155,12 @@ void setup() { 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 second before startup"); + 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. - if (gUseFastLEDApi) { - setup_i2s_using_fastled_api(); - } else { - driver = fl::InternalI2SDriver::create(); - driver->initled((uint8_t *)leds, PINS, NUMSTRIPS, NUM_LEDS_PER_STRIP); // Skips extra frame buffer copy. - driver->setBrightness(32); - } + + setup_i2s(); + FastLED.setBrightness(32); + } void fill_rainbow(CRGB* all_leds) { @@ -199,10 +176,6 @@ void fill_rainbow(CRGB* all_leds) { void loop() { fill_rainbow(leds); - if (gUseFastLEDApi) { - FastLED.show(); - } else { - FASTLED_ASSERT(driver != nullptr, "Did not expect driver to be null"); - driver->show(); - } + FastLED.show(); + } diff --git a/libraries/FastLED/examples/FestivalStick/FestivalStick.ino b/libraries/FastLED/examples/FestivalStick/FestivalStick.ino index cce9fa0..59585f6 100644 --- a/libraries/FastLED/examples/FestivalStick/FestivalStick.ino +++ b/libraries/FastLED/examples/FestivalStick/FestivalStick.ino @@ -1 +1,11 @@ -#include "old.h" \ No newline at end of file +#include "FastLED.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + +#include "curr.h" + +#endif \ No newline at end of file diff --git a/libraries/FastLED/examples/FestivalStick/curr.h b/libraries/FastLED/examples/FestivalStick/curr.h index b451a71..e24eeee 100644 --- a/libraries/FastLED/examples/FestivalStick/curr.h +++ b/libraries/FastLED/examples/FestivalStick/curr.h @@ -1,112 +1,64 @@ /* -Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of -a wooden walking stick commonly found on amazon.A0 +Basic cork screw test. -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. +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. */ #include "fl/assert.h" #include "fl/corkscrew.h" -#include "fl/screenmap.h" -#include "fl/warn.h" -#include "fl/sstream.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 // #include "vec3.h" using namespace fl; -// Power management settings -#define VOLTS 5 -#define MAX_AMPS 1 -#define PIN_DATA 9 -#define PIN_CLOCK 7 +#define PIN_DATA 3 +#define PIN_CLOCK 4 -// 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 CORKSCREW_TOTAL_LENGTH 100 // 100 cm #define CORKSCREW_TOTAL_HEIGHT \ - 23.25f // Total height of the corkscrew in centimeters for 144 densly - // wrapped up over 19 turns -#define CORKSCREW_TURNS 19 // Default to 19 turns + 23.25 // when height = 0, it's a circle. + // wrapped up over 19 turns +#define CORKSCREW_TURNS 20.5 // Default to 19 turns + // #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs // #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter -#define CORKSCREW_WIDTH 16 -#define CORKSCREW_HEIGHT 19 - - -UITitle festivalStickTitle("Festival Stick"); +UITitle festivalStickTitle("Corkscrew"); UIDescription festivalStickDescription( - "Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. " - "Super simple but very awesome looking." - "This assumes the dense 144 LEDs / meter."); + "Tests the ability to map a cork screw onto a 2D cylindrical surface"); -UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f); -UIButton button("Button"); +UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f); -CRGB leds[NUM_LEDS]; +UICheckbox allWhite("All White", false); +UICheckbox splatRendering("Splat Rendering", true); + +// CRGB leds[NUM_LEDS]; // Tested on a 288 led (2x 144 max density led strip) with 19 turns // with 23.25cm height, 19 turns, and ~15.5 LEDs per turn. -Corkscrew::Input - corkscrewInput(CORKSCREW_TOTAL_HEIGHT, - CORKSCREW_TURNS * 2.0f * PI, // Default to 19 turns - 0, // offset to account for gaps between segments - NUM_LEDS, // Default to dense 144 leds. - ); +Corkscrew::Input corkscrewInput(CORKSCREW_TOTAL_LENGTH, CORKSCREW_TOTAL_HEIGHT, + CORKSCREW_TURNS, NUM_LEDS, 0); -// Corkscrew::Output corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput); +// Corkscrew::State corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput); Corkscrew corkscrew(corkscrewInput); -// Used only for the fl::ScreenMap generation. -struct corkscrew_args { - int num_leds = NUM_LEDS; - float leds_per_turn = 15.5; - float width_cm = 1.0; -}; - -fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) { - // Create a ScreenMap for the corkscrew - fl::vector 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) @@ -117,96 +69,77 @@ fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) { // fl::vector mapCorkScrew = makeCorkScrew(args); fl::ScreenMap screenMap; +fl::Grid frameBuffer; + -CLEDController *addController() { - CLEDController *controller = - &FastLED.addLeds(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)); - corkscrew_args args = corkscrew_args(); - screenMap = makeScreenMap(args); - // screenMap = ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f); - auto controller = addController(); + int width = corkscrew.cylinder_width(); + int height = corkscrew.cylinder_height(); + + 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(leds, NUM_LEDS); + + // CLEDController *controller = + // &FastLED.addLeds(leds, num_leds); + + fl::ScreenMap screenMap = xyMap.toScreenMap(); + screenMap.setDiameter(.2f); + // 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); - 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 printOutput(const Corkscrew::Output& output) { - fl::sstream stream; - stream << "Corkscrew Output:\n"; - stream << "Width: " << output.width << "\n"; - stream << "Height: " << output.height << "\n"; - // stream << "Mapping: \n"; - // for (const auto &point : output.mapping) { - // stream << point << "\n"; - // } - FASTLED_WARN(stream.str()); -} - - - -LedsXY frameBuffer; - void loop() { uint32_t now = millis(); - fl::clear(leds); + // fl::clear(lesdds); fl::clear(frameBuffer); - static int w = 0; + static float pos = 0; - EVERY_N_MILLIS(300) { - // Update the corkscrew mapping every second - w = (w + 1) % CORKSCREW_WIDTH; + // Update the corkscrew mapping every second + // w = (w + 1) % CORKSCREW_WIDTH; + // frameBuffer. + pos += speed.value(); + if (pos > corkscrew.size() - 1) { + pos = 0; // Reset to the beginning } - - - // draw a blue line down the middle - for (int i = 0; i < CORKSCREW_HEIGHT; ++i) { - frameBuffer.at(w % CORKSCREW_WIDTH, i) = CRGB::Blue; - frameBuffer.at((w + 1) % CORKSCREW_WIDTH, i) = CRGB::Blue; - frameBuffer.at((w - 1 + CORKSCREW_WIDTH) % CORKSCREW_WIDTH, i) = CRGB::Blue; - frameBuffer.at((w + 2) % CORKSCREW_WIDTH, i) = CRGB::Blue; - frameBuffer.at((w - 2 + CORKSCREW_WIDTH) % CORKSCREW_WIDTH, i) = CRGB::Blue; + 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); + vec2i16 wrapped_pos = data.first; // Already wrapped position + uint8_t alpha = data.second; // Alpha value - - // printOutput(corkscrewMap); - - for (int i = 0; i < NUM_LEDS; ++i) { - // Get the position in the frame buffer - vec2 pos = corkscrew.at(i); - // Draw the tile to the frame buffer - CRGB c = frameBuffer.at(pos.x, pos.y); - leds[i] = c; - - FASTLED_WARN_IF(i < 16, "LED " << i << " at position: " - << pos.x << ", " << pos.y - << " with color: " << c); - + 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_exact(pos); + vec2i16 pos_i16 = vec2i16(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(); } diff --git a/libraries/FastLED/examples/FestivalStick/old.h b/libraries/FastLED/examples/FestivalStick/old.h index 55d19a3..35bd5ff 100644 --- a/libraries/FastLED/examples/FestivalStick/old.h +++ b/libraries/FastLED/examples/FestivalStick/old.h @@ -40,11 +40,11 @@ UIDescription festivalStickDescription( "Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. Super simple but very awesome looking." "This assumes the dense 144 LEDs / meter."); - - 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]; @@ -166,6 +166,8 @@ void setup() { // 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 diff --git a/libraries/FastLED/examples/FireCylinder/FireCylinder.h b/libraries/FastLED/examples/FireCylinder/FireCylinder.h new file mode 100644 index 0000000..d4e67b5 --- /dev/null +++ b/libraries/FastLED/examples/FireCylinder/FireCylinder.h @@ -0,0 +1,214 @@ + +/* +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 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(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(); + + // 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(); + + // 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(); +} diff --git a/libraries/FastLED/examples/FireCylinder/FireCylinder.ino b/libraries/FastLED/examples/FireCylinder/FireCylinder.ino index 5bc21dc..9f3e4be 100644 --- a/libraries/FastLED/examples/FireCylinder/FireCylinder.ino +++ b/libraries/FastLED/examples/FireCylinder/FireCylinder.ino @@ -1,207 +1,9 @@ - -/* -This demo is best viewed using the FastLED compiler. -Install: pip install fastled -Run: fastled -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(HEIGHT, WIDTH, 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 - FastLED.addLeds(leds, HEIGHT * WIDTH).setScreenMap(xyMap); - - // 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(); - - // 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(); - - // 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(); -} +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#include "FireCylinder.h" +#endif diff --git a/libraries/FastLED/examples/FireMatrix/FireMatrix.h b/libraries/FastLED/examples/FireMatrix/FireMatrix.h new file mode 100644 index 0000000..14ea479 --- /dev/null +++ b/libraries/FastLED/examples/FireMatrix/FireMatrix.h @@ -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 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(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(); + + // 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(); // 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 \ No newline at end of file diff --git a/libraries/FastLED/examples/FireMatrix/FireMatrix.ino b/libraries/FastLED/examples/FireMatrix/FireMatrix.ino index cdbe326..2a4a6eb 100644 --- a/libraries/FastLED/examples/FireMatrix/FireMatrix.ino +++ b/libraries/FastLED/examples/FireMatrix/FireMatrix.ino @@ -1,184 +1,9 @@ - - -/* -This demo is best viewed using the FastLED compiler. -Install: pip install fastled -Run: fastled -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 - #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(HEIGHT, WIDTH, 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 - FastLED.addLeds(leds, HEIGHT * WIDTH).setScreenMap(xyMap); - - // 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(); - - // 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(); // 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 WIDTH-1 to a value from 255 to 0 - int8_t subtraction_factor = abs8(j - (WIDTH - 1)) * 255 / (WIDTH - 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 < HEIGHT; i++) { - for (int j = 0; j < WIDTH; 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 (HEIGHT-1)-i and (WIDTH-1)-j to flip the coordinates - // This makes the fire appear to rise from the bottom - int index = xyMap((HEIGHT - 1) - i, (WIDTH - 1) - j); - - // Set the LED color in our array - leds[index] = c; - } - } - - // Send the color data to the actual LEDs - FastLED.show(); -} +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#include "FireMatrix.h" +#endif diff --git a/libraries/FastLED/examples/FxSdCard/FxSdCard.ino b/libraries/FastLED/examples/FxSdCard/FxSdCard.ino index 558cd25..f1df07d 100644 --- a/libraries/FastLED/examples/FxSdCard/FxSdCard.ino +++ b/libraries/FastLED/examples/FxSdCard/FxSdCard.ino @@ -8,8 +8,9 @@ /// 3. Run the FastLED web compiler at root: `fastled` /// 4. When the compiler is done a web page will open. +#include "FastLED.h" -#ifdef __AVR__ +#if !SKETCH_HAS_LOTS_OF_MEMORY void setup() { // put your setup code here, to run once: } diff --git a/libraries/FastLED/examples/FxWave2d/FxWave2d.ino b/libraries/FastLED/examples/FxWave2d/FxWave2d.ino index dfdea03..91d4503 100644 --- a/libraries/FastLED/examples/FxWave2d/FxWave2d.ino +++ b/libraries/FastLED/examples/FxWave2d/FxWave2d.ino @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/examples/FxWave2d/wavefx.cpp b/libraries/FastLED/examples/FxWave2d/wavefx.cpp index 10293db..b5869f9 100644 --- a/libraries/FastLED/examples/FxWave2d/wavefx.cpp +++ b/libraries/FastLED/examples/FxWave2d/wavefx.cpp @@ -1,11 +1,11 @@ - -#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 will compile and preview the sketch in the browser, and enable @@ -40,6 +40,7 @@ CRGB leds[NUM_LEDS]; 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 @@ -50,7 +51,7 @@ UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall 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) -UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop) + // Upper wave layer controls: UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates diff --git a/libraries/FastLED/examples/FxWave2d/wavefx.h b/libraries/FastLED/examples/FxWave2d/wavefx.h index d62f6ea..13903fc 100644 --- a/libraries/FastLED/examples/FxWave2d/wavefx.h +++ b/libraries/FastLED/examples/FxWave2d/wavefx.h @@ -5,6 +5,11 @@ /* 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/examples/NoisePlayground/NoisePlayground.h b/libraries/FastLED/examples/NoisePlayground/NoisePlayground.h new file mode 100644 index 0000000..117f473 --- /dev/null +++ b/libraries/FastLED/examples/NoisePlayground/NoisePlayground.h @@ -0,0 +1,83 @@ + + + +/// @file NoisePlayground.ino +/// @brief Demonstrates how to use noise generation on a 2D LED matrix +/// @example NoisePlayground.ino + +#include + + +// Params for width and height +const uint8_t kMatrixWidth = 16; +const uint8_t kMatrixHeight = 16; + +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +// Param for different pixel layouts +#define kMatrixSerpentineLayout true + +// led array +CRGB leds[kMatrixWidth * kMatrixHeight]; + +// x,y, & time values +uint32_t x,y,v_time,hue_time,hxy; + +// Play with the values of the variables below and see what kinds of effects they +// have! More octaves will make things slower. + +// how many octaves to use for the brightness and hue functions +uint8_t octaves=1; +uint8_t hue_octaves=3; + +// the 'distance' between points on the x and y axis +int xscale=57771; +int yscale=57771; + +// the 'distance' between x/y points for the hue noise +int hue_scale=1; + +// how fast we move through time & hue noise +int time_speed=1111; +int hue_speed=31; + +// adjust these values to move along the x or y axis between frames +int x_speed=331; +int y_speed=1111; + + + +void setup() { + // initialize the x/y and time values + random16_set_seed(8934); + random16_add_entropy(analogRead(3)); + + Serial.begin(57600); + Serial.println("resetting!"); + + delay(3000); + FastLED.addLeds(leds,NUM_LEDS); + FastLED.setBrightness(96); + + hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); +} + +void loop() { + // fill the led array 2/16-bit noise values + fill_2dnoise16(leds, kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout, + octaves,x,xscale,y,yscale,v_time, + hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false); + + FastLED.show(); + + // adjust the intra-frame time values + x += x_speed; + y += y_speed; + v_time += time_speed; + hue_time += hue_speed; + // delay(50); +} diff --git a/libraries/FastLED/examples/NoisePlayground/NoisePlayground.ino b/libraries/FastLED/examples/NoisePlayground/NoisePlayground.ino index 9c65cd5..4728d80 100644 --- a/libraries/FastLED/examples/NoisePlayground/NoisePlayground.ino +++ b/libraries/FastLED/examples/NoisePlayground/NoisePlayground.ino @@ -1,90 +1,11 @@ +#include // Main FastLED library for controlling LEDs - - -/// @file NoisePlayground.ino -/// @brief Demonstrates how to use noise generation on a 2D LED matrix -/// @example NoisePlayground.ino - -#include - -#if defined(__AVR__) -// too large for ATtiny85, attiny88, etc.. Just disable it for all avr boards. -void setup() {}; -void loop() {}; -#else - -// Params for width and height -const uint8_t kMatrixWidth = 16; -const uint8_t kMatrixHeight = 16; - -#define NUM_LEDS (kMatrixWidth * kMatrixHeight) - -// Param for different pixel layouts -#define kMatrixSerpentineLayout true - -// led array -CRGB leds[kMatrixWidth * kMatrixHeight]; - -// x,y, & time values -uint32_t x,y,v_time,hue_time,hxy; - -// Play with the values of the variables below and see what kinds of effects they -// have! More octaves will make things slower. - -// how many octaves to use for the brightness and hue functions -uint8_t octaves=1; -uint8_t hue_octaves=3; - -// the 'distance' between points on the x and y axis -int xscale=57771; -int yscale=57771; - -// the 'distance' between x/y points for the hue noise -int hue_scale=1; - -// how fast we move through time & hue noise -int time_speed=1111; -int hue_speed=31; - -// adjust these values to move along the x or y axis between frames -int x_speed=331; -int y_speed=1111; - -void loop() { - // fill the led array 2/16-bit noise values - fill_2dnoise16(leds, kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout, - octaves,x,xscale,y,yscale,v_time, - hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false); - - FastLED.show(); - - // adjust the intra-frame time values - x += x_speed; - y += y_speed; - v_time += time_speed; - hue_time += hue_speed; - // delay(50); -} - - -void setup() { - // initialize the x/y and time values - random16_set_seed(8934); - random16_add_entropy(analogRead(3)); - - Serial.begin(57600); - Serial.println("resetting!"); - - delay(3000); - FastLED.addLeds(leds,NUM_LEDS); - FastLED.setBrightness(96); - - hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); - x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); - y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); - v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); - hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); - -} - -#endif +#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 "NoisePlayground.h" +#endif // End of the non-AVR code section diff --git a/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h b/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h new file mode 100644 index 0000000..7095d25 --- /dev/null +++ b/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h @@ -0,0 +1,407 @@ +/// @file NoisePlusPalette.ino +/// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix +/// @example NoisePlusPalette.ino +/// +/// OVERVIEW: +/// This sketch demonstrates combining Perlin noise with color palettes to create +/// dynamic, flowing color patterns on an LED matrix. The noise function creates +/// natural-looking patterns that change over time, while the color palettes +/// determine which colors are used to visualize the noise values. + +#include // 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.) + +// LED hardware configuration +#define LED_PIN 3 // Data pin connected to the LED strip +#define BRIGHTNESS 96 // Default brightness level (0-255) +#define LED_TYPE WS2811 // Type of LED strip being used +#define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type) + +// Matrix dimensions - defines the size of our virtual LED grid +const uint8_t kMatrixWidth = 16; // Number of columns in the matrix +const uint8_t kMatrixHeight = 16; // Number of rows in the matrix + +// LED strip layout configuration +const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards + // This is common in matrix setups to allow + // for easier wiring + + +// HOW THIS EXAMPLE WORKS: +// +// 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 procedurally generated +// palettes. + + +// Calculate the total number of LEDs in our matrix +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +// Find the larger dimension (width or height) for our noise array size +#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight) + +// Array to hold all LED color values - one CRGB struct per LED +CRGB leds[kMatrixWidth * kMatrixHeight]; + +// The 16-bit version of our coordinates for the noise function +// Using 16 bits gives us more resolution and smoother animations +static uint16_t x; // x-coordinate in the noise space +static uint16_t y; // y-coordinate in the noise space +static uint16_t z; // z-coordinate (time dimension) in the noise space + +// ANIMATION PARAMETERS: + +// 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. +uint16_t speed = 20; // Speed is set dynamically once we've started up + // Higher values = faster animation + +// 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 will be. A value +// of 1 will be so zoomed in, you'll mostly see solid colors. +uint16_t scale = 30; // Scale is set dynamically once we've started up + // Higher values = more "zoomed out" pattern + +// This is the array that we keep our computed noise values in +// Each position stores an 8-bit (0-255) noise value +uint8_t noise[MAX_DIMENSION][MAX_DIMENSION]; + +// The current color palette we're using to map noise values to colors +CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors + +// If colorLoop is set to 1, we'll cycle through the colors in the palette +// This creates an additional animation effect on top of the noise movement +uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors + + +// Forward declare our functions so that we have maximum compatibility +// with other build tools outside of ArduinoIDE. The *.ino files are +// special in that Arduino will generate function prototypes for you. +// For non-Arduino environments, we need these declarations. +void SetupRandomPalette(); // Creates a random color palette +void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette +void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette +void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time +void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette +uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index + +void setup() { + delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open + + // Initialize the LED strip: + // - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.) + // - LED_PIN is the data pin number + // - COLOR_ORDER specifies the RGB color ordering for your strip + FastLED.addLeds(leds,NUM_LEDS); + // NOTE - This does NOT have a ScreenMap (because it's a legacy sketch) + // so it won't look that good on the web-compiler. But adding it is ONE LINE! + + // Set the overall brightness level (0-255) + FastLED.setBrightness(BRIGHTNESS); + + // Initialize our noise coordinates to random values + // This ensures the pattern starts from a different position each time + x = random16(); // Random x starting position + y = random16(); // Random y starting position + z = random16(); // Random time starting position +} + + + +// Fill the x/y array of 8-bit noise values using the inoise8 function. +void fillnoise8() { + // If we're running at a low "speed", some 8-bit artifacts become visible + // from frame-to-frame. In order to reduce this, we can do some fast data-smoothing. + // The amount of data smoothing we're doing depends on "speed". + uint8_t dataSmoothing = 0; + if( speed < 50) { + // At lower speeds, apply more smoothing + // This formula creates more smoothing at lower speeds: + // speed=10 → smoothing=160, speed=30 → smoothing=80 + dataSmoothing = 200 - (speed * 4); + } + + // Loop through each pixel in our noise array + for(int i = 0; i < MAX_DIMENSION; i++) { + // Calculate the offset for this pixel in the x dimension + int ioffset = scale * i; + + for(int j = 0; j < MAX_DIMENSION; j++) { + // Calculate the offset for this pixel in the y dimension + int joffset = scale * j; + + // Generate the noise value for this pixel using 3D Perlin noise + // The noise function takes x, y, and z (time) coordinates + uint8_t data = inoise8(x + ioffset, y + joffset, z); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + // You can comment them out if you want the raw noise data. + data = qsub8(data, 16); // Subtract 16 (with underflow protection) + data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself + + // Apply data smoothing if enabled + if( dataSmoothing ) { + uint8_t olddata = noise[i][j]; // Get the previous frame's value + + // Blend between old and new data based on smoothing amount + // Higher dataSmoothing = more of the old value is kept + uint8_t newdata = scale8(olddata, dataSmoothing) + + scale8(data, 256 - dataSmoothing); + data = newdata; + } + + // Store the final noise value in our array + noise[i][j] = data; + } + } + + // Increment z to move through the noise space over time + z += speed; + + // Apply slow drift to X and Y, just for visual variation + // This creates a gentle shifting of the entire pattern + x += speed / 8; // X drifts at 1/8 the speed of z + y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction +} + + + +// Map the noise data to LED colors using the current color palette +void mapNoiseToLEDsUsingPalette() +{ + // Static variable that slowly increases to cycle through colors when colorLoop is enabled + static uint8_t ihue=0; + + // Loop through each pixel in our LED matrix + for(int i = 0; i < kMatrixWidth; i++) { + for(int j = 0; j < kMatrixHeight; j++) { + // We use the value at the (i,j) coordinate in the noise + // array for our brightness, and the flipped value from (j,i) + // for our pixel's index into the color palette. + // This creates interesting patterns with two different noise mappings. + + uint8_t index = noise[j][i]; // Color index from the flipped coordinate + uint8_t bri = noise[i][j]; // Brightness from the normal coordinate + + // If color cycling is enabled, add a slowly-changing base value to the index + // This makes the colors shift/rotate through the palette over time + if( colorLoop) { + index += ihue; // Add the slowly increasing hue offset + } + + // Brighten up the colors, as the color palette itself often contains the + // light/dark dynamic range desired + if( bri > 127 ) { + // If brightness is in the upper half, make it full brightness + bri = 255; + } else { + // Otherwise, scale it to the full range (0-127 becomes 0-254) + bri = dim8_raw( bri * 2); + } + + // Get the final color by looking up the palette color at our index + // and applying the brightness value + CRGB color = ColorFromPalette( currentPalette, index, bri); + + // Set the LED color in our array, using the XY mapping function + // to convert from x,y coordinates to the 1D array index + leds[XY(i,j)] = color; + } + } + + // Increment the hue value for the next frame (for color cycling) + ihue+=1; +} + +void loop() { + // The main program loop that runs continuously + + // Periodically choose a new palette, speed, and scale + // This creates variety in the animation over time + ChangePaletteAndSettingsPeriodically(); + + // Generate new noise data for this frame + fillnoise8(); + + // Convert the noise data to colors in the LED array + // using the current palette + mapNoiseToLEDsUsingPalette(); + + // Send the color data to the actual LEDs + FastLED.show(); + + // No delay is needed here as the calculations already take some time + // Adding a delay would slow down the animation + // delay(10); +} + + + +// PALETTE MANAGEMENT: +// +// 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. + +// This controls how long each palette is displayed before changing +// 1 = 5 sec per palette +// 2 = 10 sec per palette +// etc. +#define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration + +// Periodically change the palette, speed, and scale settings +void ChangePaletteAndSettingsPeriodically() +{ + // Calculate which "second hand" we're on (0-59) based on elapsed time + // We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes + uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60; + static uint8_t lastSecond = 99; // Track the last second to detect changes + + // Only update when the second hand changes + if( lastSecond != secondHand) { + lastSecond = secondHand; + + // Every 5 seconds, change to a different palette and settings + // Each palette has specific speed and scale settings that work well with it + + if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; } + if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; } + if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; } + if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; } + if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; } + if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; } + if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; } + if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; } + if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; } + } +} + + + +// This function generates a random palette that's a gradient +// between four different colors. The first is a dim hue, the second is +// a bright hue, the third is a bright pastel, and the last is +// another bright hue. This gives some visual bright/dark variation +// which is more interesting than just a gradient of different hues. +void SetupRandomPalette() +{ + // Create a new palette with 4 random colors that blend together + currentPalette = CRGBPalette16( + CHSV( random8(), 255, 32), // Random dim hue (low value) + CHSV( random8(), 255, 255), // Random bright hue (full saturation & value) + CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value) + CHSV( random8(), 255, 255)); // Another random bright hue + + // The CRGBPalette16 constructor automatically creates a 16-color gradient + // between these four colors, evenly distributed +} + + +// 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 to create stripes + // Positions 0, 4, 8, and 12 in the 16-color palette + currentPalette[0] = CRGB::White; + currentPalette[4] = CRGB::White; + currentPalette[8] = CRGB::White; + currentPalette[12] = CRGB::White; + + // The palette interpolation will create smooth transitions between these colors +} + +// This function sets up a palette of purple and green stripes. +void SetupPurpleAndGreenPalette() +{ + // Define our colors using HSV color space for consistency + CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple + CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green + CRGB black = CRGB::Black; // Black + + // Create a 16-color palette with a specific pattern: + // green-green-black-black-purple-purple-black-black, repeated twice + // This creates alternating green and purple stripes with black in between + currentPalette = CRGBPalette16( + green, green, black, black, // First 4 colors + purple, purple, black, black, // Next 4 colors + green, green, black, black, // Repeat the pattern + purple, purple, black, black ); // Last 4 colors +} + + +// +// Mark's xy coordinate mapping code. See the XYMatrix for more information on it. +// +// This function converts x,y coordinates to a single array index +// It handles both regular and serpentine matrix layouts +uint16_t XY( uint8_t x, uint8_t y) +{ + uint16_t i; + + // For a regular/sequential layout, it's just y * width + x + if( kMatrixSerpentineLayout == false) { + i = (y * kMatrixWidth) + x; + } + + // For a serpentine layout (zigzag), odd rows run backwards + if( kMatrixSerpentineLayout == true) { + if( y & 0x01) { // Check if y is odd (bitwise AND with 1) + // Odd rows run backwards + uint8_t reverseX = (kMatrixWidth - 1) - x; + i = (y * kMatrixWidth) + reverseX; + } else { + // Even rows run forwards + i = (y * kMatrixWidth) + x; + } + } + + return i; +} + + +#endif // End of the non-AVR code section diff --git a/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino b/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino index b07aae3..ecc6f64 100644 --- a/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino +++ b/libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino @@ -1,407 +1,11 @@ -/// @file NoisePlusPalette.ino -/// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix -/// @example NoisePlusPalette.ino -/// -/// OVERVIEW: -/// This sketch demonstrates combining Perlin noise with color palettes to create -/// dynamic, flowing color patterns on an LED matrix. The noise function creates -/// natural-looking patterns that change over time, while the color palettes -/// determine which colors are used to visualize the noise values. - #include // Main FastLED library for controlling LEDs -#ifdef __AVR__ +#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.) - -// LED hardware configuration -#define LED_PIN 3 // Data pin connected to the LED strip -#define BRIGHTNESS 96 // Default brightness level (0-255) -#define LED_TYPE WS2811 // Type of LED strip being used -#define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type) - -// Matrix dimensions - defines the size of our virtual LED grid -const uint8_t kMatrixWidth = 16; // Number of columns in the matrix -const uint8_t kMatrixHeight = 16; // Number of rows in the matrix - -// LED strip layout configuration -const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards - // This is common in matrix setups to allow - // for easier wiring - - -// HOW THIS EXAMPLE WORKS: -// -// 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 procedurally generated -// palettes. - - -// Calculate the total number of LEDs in our matrix -#define NUM_LEDS (kMatrixWidth * kMatrixHeight) - -// Find the larger dimension (width or height) for our noise array size -#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight) - -// Array to hold all LED color values - one CRGB struct per LED -CRGB leds[kMatrixWidth * kMatrixHeight]; - -// The 16-bit version of our coordinates for the noise function -// Using 16 bits gives us more resolution and smoother animations -static uint16_t x; // x-coordinate in the noise space -static uint16_t y; // y-coordinate in the noise space -static uint16_t z; // z-coordinate (time dimension) in the noise space - -// ANIMATION PARAMETERS: - -// 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. -uint16_t speed = 20; // Speed is set dynamically once we've started up - // Higher values = faster animation - -// 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 will be. A value -// of 1 will be so zoomed in, you'll mostly see solid colors. -uint16_t scale = 30; // Scale is set dynamically once we've started up - // Higher values = more "zoomed out" pattern - -// This is the array that we keep our computed noise values in -// Each position stores an 8-bit (0-255) noise value -uint8_t noise[MAX_DIMENSION][MAX_DIMENSION]; - -// The current color palette we're using to map noise values to colors -CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors - -// If colorLoop is set to 1, we'll cycle through the colors in the palette -// This creates an additional animation effect on top of the noise movement -uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors - - -// Forward declare our functions so that we have maximum compatibility -// with other build tools outside of ArduinoIDE. The *.ino files are -// special in that Arduino will generate function prototypes for you. -// For non-Arduino environments, we need these declarations. -void SetupRandomPalette(); // Creates a random color palette -void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette -void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette -void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time -void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette -uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index - -void setup() { - delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open - - // Initialize the LED strip: - // - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.) - // - LED_PIN is the data pin number - // - COLOR_ORDER specifies the RGB color ordering for your strip - FastLED.addLeds(leds,NUM_LEDS); - // NOTE - This does NOT have a ScreenMap (because it's a legacy sketch) - // so it won't look that good on the web-compiler. But adding it is ONE LINE! - - // Set the overall brightness level (0-255) - FastLED.setBrightness(BRIGHTNESS); - - // Initialize our noise coordinates to random values - // This ensures the pattern starts from a different position each time - x = random16(); // Random x starting position - y = random16(); // Random y starting position - z = random16(); // Random time starting position -} - - - -// Fill the x/y array of 8-bit noise values using the inoise8 function. -void fillnoise8() { - // If we're running at a low "speed", some 8-bit artifacts become visible - // from frame-to-frame. In order to reduce this, we can do some fast data-smoothing. - // The amount of data smoothing we're doing depends on "speed". - uint8_t dataSmoothing = 0; - if( speed < 50) { - // At lower speeds, apply more smoothing - // This formula creates more smoothing at lower speeds: - // speed=10 → smoothing=160, speed=30 → smoothing=80 - dataSmoothing = 200 - (speed * 4); - } - - // Loop through each pixel in our noise array - for(int i = 0; i < MAX_DIMENSION; i++) { - // Calculate the offset for this pixel in the x dimension - int ioffset = scale * i; - - for(int j = 0; j < MAX_DIMENSION; j++) { - // Calculate the offset for this pixel in the y dimension - int joffset = scale * j; - - // Generate the noise value for this pixel using 3D Perlin noise - // The noise function takes x, y, and z (time) coordinates - uint8_t data = inoise8(x + ioffset, y + joffset, z); - - // The range of the inoise8 function is roughly 16-238. - // These two operations expand those values out to roughly 0..255 - // You can comment them out if you want the raw noise data. - data = qsub8(data, 16); // Subtract 16 (with underflow protection) - data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself - - // Apply data smoothing if enabled - if( dataSmoothing ) { - uint8_t olddata = noise[i][j]; // Get the previous frame's value - - // Blend between old and new data based on smoothing amount - // Higher dataSmoothing = more of the old value is kept - uint8_t newdata = scale8(olddata, dataSmoothing) + - scale8(data, 256 - dataSmoothing); - data = newdata; - } - - // Store the final noise value in our array - noise[i][j] = data; - } - } - - // Increment z to move through the noise space over time - z += speed; - - // Apply slow drift to X and Y, just for visual variation - // This creates a gentle shifting of the entire pattern - x += speed / 8; // X drifts at 1/8 the speed of z - y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction -} - - - -// Map the noise data to LED colors using the current color palette -void mapNoiseToLEDsUsingPalette() -{ - // Static variable that slowly increases to cycle through colors when colorLoop is enabled - static uint8_t ihue=0; - - // Loop through each pixel in our LED matrix - for(int i = 0; i < kMatrixWidth; i++) { - for(int j = 0; j < kMatrixHeight; j++) { - // We use the value at the (i,j) coordinate in the noise - // array for our brightness, and the flipped value from (j,i) - // for our pixel's index into the color palette. - // This creates interesting patterns with two different noise mappings. - - uint8_t index = noise[j][i]; // Color index from the flipped coordinate - uint8_t bri = noise[i][j]; // Brightness from the normal coordinate - - // If color cycling is enabled, add a slowly-changing base value to the index - // This makes the colors shift/rotate through the palette over time - if( colorLoop) { - index += ihue; // Add the slowly increasing hue offset - } - - // Brighten up the colors, as the color palette itself often contains the - // light/dark dynamic range desired - if( bri > 127 ) { - // If brightness is in the upper half, make it full brightness - bri = 255; - } else { - // Otherwise, scale it to the full range (0-127 becomes 0-254) - bri = dim8_raw( bri * 2); - } - - // Get the final color by looking up the palette color at our index - // and applying the brightness value - CRGB color = ColorFromPalette( currentPalette, index, bri); - - // Set the LED color in our array, using the XY mapping function - // to convert from x,y coordinates to the 1D array index - leds[XY(i,j)] = color; - } - } - - // Increment the hue value for the next frame (for color cycling) - ihue+=1; -} - -void loop() { - // The main program loop that runs continuously - - // Periodically choose a new palette, speed, and scale - // This creates variety in the animation over time - ChangePaletteAndSettingsPeriodically(); - - // Generate new noise data for this frame - fillnoise8(); - - // Convert the noise data to colors in the LED array - // using the current palette - mapNoiseToLEDsUsingPalette(); - - // Send the color data to the actual LEDs - FastLED.show(); - - // No delay is needed here as the calculations already take some time - // Adding a delay would slow down the animation - // delay(10); -} - - - -// PALETTE MANAGEMENT: -// -// 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. - -// This controls how long each palette is displayed before changing -// 1 = 5 sec per palette -// 2 = 10 sec per palette -// etc. -#define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration - -// Periodically change the palette, speed, and scale settings -void ChangePaletteAndSettingsPeriodically() -{ - // Calculate which "second hand" we're on (0-59) based on elapsed time - // We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes - uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60; - static uint8_t lastSecond = 99; // Track the last second to detect changes - - // Only update when the second hand changes - if( lastSecond != secondHand) { - lastSecond = secondHand; - - // Every 5 seconds, change to a different palette and settings - // Each palette has specific speed and scale settings that work well with it - - if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; } - if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; } - if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; } - if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; } - if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; } - if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; } - if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; } - if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; } - if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; } - if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; } - if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; } - if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; } - } -} - - - -// This function generates a random palette that's a gradient -// between four different colors. The first is a dim hue, the second is -// a bright hue, the third is a bright pastel, and the last is -// another bright hue. This gives some visual bright/dark variation -// which is more interesting than just a gradient of different hues. -void SetupRandomPalette() -{ - // Create a new palette with 4 random colors that blend together - currentPalette = CRGBPalette16( - CHSV( random8(), 255, 32), // Random dim hue (low value) - CHSV( random8(), 255, 255), // Random bright hue (full saturation & value) - CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value) - CHSV( random8(), 255, 255)); // Another random bright hue - - // The CRGBPalette16 constructor automatically creates a 16-color gradient - // between these four colors, evenly distributed -} - - -// 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 to create stripes - // Positions 0, 4, 8, and 12 in the 16-color palette - currentPalette[0] = CRGB::White; - currentPalette[4] = CRGB::White; - currentPalette[8] = CRGB::White; - currentPalette[12] = CRGB::White; - - // The palette interpolation will create smooth transitions between these colors -} - -// This function sets up a palette of purple and green stripes. -void SetupPurpleAndGreenPalette() -{ - // Define our colors using HSV color space for consistency - CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple - CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green - CRGB black = CRGB::Black; // Black - - // Create a 16-color palette with a specific pattern: - // green-green-black-black-purple-purple-black-black, repeated twice - // This creates alternating green and purple stripes with black in between - currentPalette = CRGBPalette16( - green, green, black, black, // First 4 colors - purple, purple, black, black, // Next 4 colors - green, green, black, black, // Repeat the pattern - purple, purple, black, black ); // Last 4 colors -} - - -// -// Mark's xy coordinate mapping code. See the XYMatrix for more information on it. -// -// This function converts x,y coordinates to a single array index -// It handles both regular and serpentine matrix layouts -uint16_t XY( uint8_t x, uint8_t y) -{ - uint16_t i; - - // For a regular/sequential layout, it's just y * width + x - if( kMatrixSerpentineLayout == false) { - i = (y * kMatrixWidth) + x; - } - - // For a serpentine layout (zigzag), odd rows run backwards - if( kMatrixSerpentineLayout == true) { - if( y & 0x01) { // Check if y is odd (bitwise AND with 1) - // Odd rows run backwards - uint8_t reverseX = (kMatrixWidth - 1) - x; - i = (y * kMatrixWidth) + reverseX; - } else { - // Even rows run forwards - i = (y * kMatrixWidth) + x; - } - } - - return i; -} - - +#include "NoisePlusPalette.h" #endif // End of the non-AVR code section diff --git a/libraries/FastLED/examples/Overclock/Overclock.ino b/libraries/FastLED/examples/Overclock/Overclock.ino index 6fb1546..8c62827 100644 --- a/libraries/FastLED/examples/Overclock/Overclock.ino +++ b/libraries/FastLED/examples/Overclock/Overclock.ino @@ -1,8 +1,9 @@ /// @file Overclock.ino /// @brief Demonstrates how to overclock a FastLED setup +#include "FastLED.h" -#ifdef __AVR__ +#if !SKETCH_HAS_LOTS_OF_MEMORY // To effectively test the overclock feature we need // a large enough dataset to test against. Unfortunately // the avr platforms don't have enough memory so this example diff --git a/libraries/FastLED/examples/Pride2015/Pride2015.ino b/libraries/FastLED/examples/Pride2015/Pride2015.ino index bc2fc0d..90707d3 100644 --- a/libraries/FastLED/examples/Pride2015/Pride2015.ino +++ b/libraries/FastLED/examples/Pride2015/Pride2015.ino @@ -8,9 +8,6 @@ // Animated, ever-changing rainbows. // by Mark Kriegsman -#if FASTLED_VERSION < 3001000 -#error "Requires FastLED 3.1 or later; check github for latest code." -#endif #define DATA_PIN 3 //#define CLK_PIN 4 diff --git a/libraries/FastLED/examples/Wave/Wave.ino b/libraries/FastLED/examples/Wave/Wave.ino index fb95657..5561ca2 100644 --- a/libraries/FastLED/examples/Wave/Wave.ino +++ b/libraries/FastLED/examples/Wave/Wave.ino @@ -4,6 +4,11 @@ This is a 1D wave simluation! 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/examples/Wave2d/Wave2d.ino b/libraries/FastLED/examples/Wave2d/Wave2d.ino index de4632b..4b8f6e1 100644 --- a/libraries/FastLED/examples/Wave2d/Wave2d.ino +++ b/libraries/FastLED/examples/Wave2d/Wave2d.ino @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/examples/XYPath/complex.h b/libraries/FastLED/examples/XYPath/complex.h index 630bab9..e8dd5bd 100644 --- a/libraries/FastLED/examples/XYPath/complex.h +++ b/libraries/FastLED/examples/XYPath/complex.h @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/examples/XYPath/direct.h b/libraries/FastLED/examples/XYPath/direct.h index 51a5985..3a0e331 100644 --- a/libraries/FastLED/examples/XYPath/direct.h +++ b/libraries/FastLED/examples/XYPath/direct.h @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable @@ -27,7 +32,6 @@ all the UI elements you see below. #include "src/xypaths.h" #include "fl/function.h" -#include using namespace fl; @@ -37,8 +41,6 @@ using namespace fl; #define IS_SERPINTINE true #define TIME_ANIMATION 1000 // ms -// CRGB leds[NUM_LEDS]; - LedsXY leds; XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); UITitle title("Simple control of an xy path"); @@ -49,31 +51,21 @@ UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f); UISlider steps("Steps", 100.0f, 1.0f, 200.0f, 1.0f); UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f); - XYPathPtr heartPath = XYPath::NewHeartPath(WIDTH, HEIGHT); - - void setup() { Serial.begin(115200); auto screenmap = xyMap.toScreenMap(); screenmap.setDiameter(.2); FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); - } - void loop() { fl::clear(leds); - // FASTLED_ASSERT(false, "This is a test"); - // leds(x,y) = CRGB(255, 0, 0); float from = offset; float to = length.value() + offset.value(); - heartPath->drawColor(CRGB(255, 0, 0), from, to, &leds, steps.as_int()); - - FastLED.show(); } diff --git a/libraries/FastLED/examples/XYPath/simple.h b/libraries/FastLED/examples/XYPath/simple.h index 041c89d..452cf61 100644 --- a/libraries/FastLED/examples/XYPath/simple.h +++ b/libraries/FastLED/examples/XYPath/simple.h @@ -2,6 +2,11 @@ /* 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 will compile and preview the sketch in the browser, and enable diff --git a/libraries/FastLED/library.json b/libraries/FastLED/library.json index 0274da4..80687e4 100644 --- a/libraries/FastLED/library.json +++ b/libraries/FastLED/library.json @@ -38,7 +38,7 @@ "type": "git", "url": "https://github.com/FastLED/FastLED.git" }, - "version": "3.9.20", + "version": "3.10.1", "license": "MIT", "homepage": "http://fastled.io", "frameworks": "arduino", diff --git a/libraries/FastLED/library.properties b/libraries/FastLED/library.properties index 6ff316f..fb45793 100644 --- a/libraries/FastLED/library.properties +++ b/libraries/FastLED/library.properties @@ -1,5 +1,5 @@ name=FastLED -version=3.9.20 +version=3.10.1 author=Daniel Garcia maintainer=Daniel Garcia sentence=Multi-platform library for controlling dozens of different types of LEDs along with optimized math, effect, and noise functions. diff --git a/libraries/FastLED/platformio.ini b/libraries/FastLED/platformio.ini index e07a875..c396c62 100644 --- a/libraries/FastLED/platformio.ini +++ b/libraries/FastLED/platformio.ini @@ -5,7 +5,7 @@ default_envs = dev [env:generic-esp] # Developement branch of the open source espressif32 platform -platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip framework = arduino upload_protocol = esptool monitor_filters = @@ -41,9 +41,6 @@ extends = env:generic-esp board = seeed_xiao_esp32s3 build_flags = ${env:generic-esp.build_flags} - -DBOARD_HAS_PSRAM - -mfix-esp32-psram-cache-issue - -mfix-esp32-psram-cache-strategy=memw board_build.partitions = huge_app.csv [env:esp32c6] diff --git a/libraries/FastLED/release_notes.md b/libraries/FastLED/release_notes.md index 0d9b8c7..fb212e9 100644 --- a/libraries/FastLED/release_notes.md +++ b/libraries/FastLED/release_notes.md @@ -1,4 +1,18 @@ -FastLED 3.9.18 + 3.9.19 +FastLED 3.10.0 +============== + + * Animartrix now out of beta. + * examples/Animartrix/Animartrix.ino + * ESP32 + * Esp32P4 now officially supported. + * ESP32-S3 I2S driver is improved + * It will now auto error on known bad Esp32-Arduino Core versions. + * Arudino core 3.2.0 is now know to work. + * Documentation has been greatly simplified and unnecessary steps have been removed. + + + +FastLED 3.9.18 + 3.9.19 + 3.9.20 ============== * Hotfixes for AVR platforms for 3.9.17 diff --git a/libraries/FastLED/src/FastLED.cpp b/libraries/FastLED/src/FastLED.cpp index 0dd889c..ce5471d 100644 --- a/libraries/FastLED/src/FastLED.cpp +++ b/libraries/FastLED/src/FastLED.cpp @@ -57,7 +57,7 @@ uint8_t get_brightness(); void *pSmartMatrix = NULL; FL_DISABLE_WARNING_PUSH -FL_DISABLE_WARNING(global-constructors) +FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED; // global constructor allowed in this case. diff --git a/libraries/FastLED/src/FastLED.h b/libraries/FastLED/src/FastLED.h index e588379..82999a9 100644 --- a/libraries/FastLED/src/FastLED.h +++ b/libraries/FastLED/src/FastLED.h @@ -16,13 +16,13 @@ /// * 1 digit for the major version /// * 3 digits for the minor version /// * 3 digits for the patch version -#define FASTLED_VERSION 3009020 +#define FASTLED_VERSION 301001 #ifndef FASTLED_INTERNAL # ifdef FASTLED_SHOW_VERSION # ifdef FASTLED_HAS_PRAGMA_MESSAGE -# pragma message "FastLED version 3.009.020" +# pragma message "FastLED version 3.010.001" # else -# warning FastLED version 3.009.020 (Not really a warning, just telling you here.) +# warning FastLED version 3.010.001 (Not really a warning, just telling you here.) # endif # endif #endif @@ -968,3 +968,6 @@ using namespace fl; void loop() { FASTLED_WARN("hijacked the loop"); real_loop(); } \ void real_loop() #endif + + +#include "fl/sketch_macros.h" \ No newline at end of file diff --git a/libraries/FastLED/src/crgb.cpp b/libraries/FastLED/src/crgb.cpp index 95f8043..bce7ecf 100644 --- a/libraries/FastLED/src/crgb.cpp +++ b/libraries/FastLED/src/crgb.cpp @@ -6,7 +6,7 @@ #include "FastLED.h" #include "fl/xymap.h" -#include "fl/bilinear_expansion.h" +#include "fl/upscale.h" #include "fl/downscale.h" #include "lib8tion/math8.h" @@ -82,7 +82,7 @@ void CRGB::upscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst, "Upscaling only works with a src matrix that is rectangular"); uint16_t w = srcXY.getWidth(); uint16_t h = srcXY.getHeight(); - fl::bilinearExpand(src, dst, w, h, dstXY); + fl::upscale(src, dst, w, h, dstXY); } CRGB &CRGB::nscale8(uint8_t scaledown) { diff --git a/libraries/FastLED/src/fl/algorithm.h b/libraries/FastLED/src/fl/algorithm.h index 3d2f2e1..68ecd76 100644 --- a/libraries/FastLED/src/fl/algorithm.h +++ b/libraries/FastLED/src/fl/algorithm.h @@ -11,4 +11,159 @@ void reverse(Iterator first, Iterator last) { } } +template +Iterator max_element(Iterator first, Iterator last) { + if (first == last) { + return last; + } + + Iterator max_iter = first; + ++first; + + while (first != last) { + if (*max_iter < *first) { + max_iter = first; + } + ++first; + } + + return max_iter; +} + +template +Iterator max_element(Iterator first, Iterator last, Compare comp) { + if (first == last) { + return last; + } + + Iterator max_iter = first; + ++first; + + while (first != last) { + if (comp(*max_iter, *first)) { + max_iter = first; + } + ++first; + } + + return max_iter; +} + +template +Iterator min_element(Iterator first, Iterator last) { + if (first == last) { + return last; + } + + Iterator min_iter = first; + ++first; + + while (first != last) { + if (*first < *min_iter) { + min_iter = first; + } + ++first; + } + + return min_iter; +} + +template +Iterator min_element(Iterator first, Iterator last, Compare comp) { + if (first == last) { + return last; + } + + Iterator min_iter = first; + ++first; + + while (first != last) { + if (comp(*first, *min_iter)) { + min_iter = first; + } + ++first; + } + + return min_iter; +} + + + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) { + while (first1 != last1) { + if (*first1 != *first2) { + return false; + } + ++first1; + ++first2; + } + return true; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) { + while (first1 != last1) { + if (!pred(*first1, *first2)) { + return false; + } + ++first1; + ++first2; + } + return true; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) { + while (first1 != last1 && first2 != last2) { + if (*first1 != *first2) { + return false; + } + ++first1; + ++first2; + } + return first1 == last1 && first2 == last2; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, BinaryPredicate pred) { + while (first1 != last1 && first2 != last2) { + if (!pred(*first1, *first2)) { + return false; + } + ++first1; + ++first2; + } + return first1 == last1 && first2 == last2; +} + +template +bool equal_container(const Container1& c1, const Container2& c2) { + size_t size1 = c1.size(); + size_t size2 = c2.size(); + if (size1 != size2) { + return false; + } + return equal(c1.begin(), c1.end(), c2.begin(), c2.end()); +} + +template +bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) { + size_t size1 = c1.size(); + size_t size2 = c2.size(); + if (size1 != size2) { + return false; + } + return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred); +} + + +template +void fill(Iterator first, Iterator last, const T& value) { + while (first != last) { + *first = value; + ++first; + } +} + } // namespace fl diff --git a/libraries/FastLED/src/fl/array.h b/libraries/FastLED/src/fl/array.h index 776752c..4252142 100644 --- a/libraries/FastLED/src/fl/array.h +++ b/libraries/FastLED/src/fl/array.h @@ -175,7 +175,7 @@ void swap(array &lhs, TYPE *NAME = reinterpret_cast(alloca(sizeof(TYPE) * (SIZE))); \ memset(NAME, 0, sizeof(TYPE) * (SIZE)) #elif __has_include() -#include +#include // ok include #define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \ TYPE *NAME = reinterpret_cast(alloca(sizeof(TYPE) * (SIZE))); \ memset(NAME, 0, sizeof(TYPE) * (SIZE)) diff --git a/libraries/FastLED/src/fl/bilinear_expansion.h b/libraries/FastLED/src/fl/bilinear_expansion.h index 18275a9..d2f93d8 100644 --- a/libraries/FastLED/src/fl/bilinear_expansion.h +++ b/libraries/FastLED/src/fl/bilinear_expansion.h @@ -7,7 +7,9 @@ #include #include "crgb.h" +#include "fl/deprecated.h" #include "fl/namespace.h" +#include "fl/upscale.h" #include "fl/xymap.h" namespace fl { @@ -19,47 +21,62 @@ namespace fl { /// @param inputHeight The height of the input grid. /// @param xyMap The XYMap to use to determine where to write the pixel. If the /// pixel is mapped outside of the range then it is clipped. + void bilinearExpandArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth, uint16_t inputHeight, - fl::XYMap xyMap); + fl::XYMap xyMap) + FASTLED_DEPRECATED("use upscaleArbitrary from upscale.h"); -/// @brief Performs bilinear interpolation for upscaling an image. -/// @param output The output grid to write into the interpolated values. -/// @param input The input grid to read from. -/// @param inputWidth The width of the input grid. -/// @param inputHeight The height of the input grid. -/// @param xyMap The XYMap to use to determine where to write the pixel. If the -/// pixel is mapped outside of the range then it is clipped. void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, - uint8_t inputHeight, fl::XYMap xyMap); + uint8_t inputHeight, fl::XYMap xyMap) + FASTLED_DEPRECATED("use upscalePowerOf2 from upscale.h"); -// -inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth, - uint16_t inputHeight, fl::XYMap xyMap) { - uint16_t outputWidth = xyMap.getWidth(); - uint16_t outputHeight = xyMap.getHeight(); - const bool wontFit = - (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()); - // if the input dimensions are not a power of 2 then we can't use the - // optimized version. - if (wontFit || (inputWidth & (inputWidth - 1)) || - (inputHeight & (inputHeight - 1))) { - bilinearExpandArbitrary(input, output, inputWidth, inputHeight, xyMap); - } else { - bilinearExpandPowerOf2(input, output, inputWidth, inputHeight, xyMap); - } -} +void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, fl::XYMap xyMap) + FASTLED_DEPRECATED("use upscale from upscale.h"); -// These are here for testing purposes and are slow. Their primary use -// is to test against the fixed integer version above. void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, - uint8_t inputHeight, fl::XYMap xyMap); + uint8_t inputHeight, fl::XYMap xyMap) + FASTLED_DEPRECATED("use upscaleFloat from upscale.h"); void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth, uint16_t inputHeight, - fl::XYMap xyMap); + fl::XYMap xyMap) + FASTLED_DEPRECATED("use upscaleArbitraryFloat from upscale.h"); uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01, - uint8_t v11, float dx, float dy); + uint8_t v11, float dx, float dy) + FASTLED_DEPRECATED("use upscaleFloat from upscale.h"); + +////////////////// Inline definitions for backward compatibility /////////////////////// + +inline void bilinearExpandArbitrary(const CRGB *input, CRGB *output, + uint16_t inputWidth, uint16_t inputHeight, + fl::XYMap xyMap) { + upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap); +} + +inline void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, + uint8_t inputWidth, uint8_t inputHeight, + fl::XYMap xyMap) { + upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap); +} + +inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, fl::XYMap xyMap) { + upscale(input, output, inputWidth, inputHeight, xyMap); +} + +inline void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, + uint16_t inputWidth, + uint16_t inputHeight, + fl::XYMap xyMap) { + upscaleArbitraryFloat(input, output, inputWidth, inputHeight, xyMap); +} + +inline uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01, + uint8_t v11, float dx, float dy) { + return upscaleFloat(v00, v10, v01, v11, dx, dy); +} } // namespace fl diff --git a/libraries/FastLED/src/fl/clear.h b/libraries/FastLED/src/fl/clear.h index e11ec44..64e0870 100644 --- a/libraries/FastLED/src/fl/clear.h +++ b/libraries/FastLED/src/fl/clear.h @@ -3,7 +3,12 @@ #include "fl/leds.h" #include "fl/stdint.h" + namespace fl { + +template +class Grid; + // Memory safe clear function for CRGB arrays. template inline void clear(CRGB (&arr)[N]) { for (int i = 0; i < N; ++i) { @@ -18,4 +23,17 @@ inline void clear(LedsXY &leds) { leds.fill(CRGB::Black); } +template +inline void clear(Grid &grid) { + grid.clear(); +} + +// Default, when you don't know what do then call clear. +template +inline void clear(Container &container) { + container.clear(); +} + + + } // namespace fl \ No newline at end of file diff --git a/libraries/FastLED/src/fl/compiler_control.h b/libraries/FastLED/src/fl/compiler_control.h index 3b2a005..00ff8c7 100644 --- a/libraries/FastLED/src/fl/compiler_control.h +++ b/libraries/FastLED/src/fl/compiler_control.h @@ -19,3 +19,11 @@ #define FL_DISABLE_WARNING_POP #define FL_DISABLE_WARNING(warning) #endif + + +#if defined(__clang__) + #define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \ + FL_DISABLE_WARNING(global-constructors) +#else + #define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS /* nothing */ +#endif \ No newline at end of file diff --git a/libraries/FastLED/src/fl/corkscrew.cpp b/libraries/FastLED/src/fl/corkscrew.cpp index 455d374..cd8fa5c 100644 --- a/libraries/FastLED/src/fl/corkscrew.cpp +++ b/libraries/FastLED/src/fl/corkscrew.cpp @@ -1,138 +1,142 @@ #include "fl/corkscrew.h" #include "fl/algorithm.h" +#include "fl/assert.h" #include "fl/math.h" #include "fl/splat.h" +#include "fl/math_macros.h" + #define TWO_PI (PI * 2.0) namespace fl { -void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output); +void generateState(const Corkscrew::Input &input, CorkscrewState *output); -void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output) { - // Calculate circumference per turn from height and total angle - float circumferencePerTurn = input.totalHeight * TWO_PI / input.totalAngle; +void generateState(const Corkscrew::Input &input, CorkscrewState *output) { // Calculate vertical segments based on number of turns // For a single turn (2π), we want exactly 1 vertical segment // For two turns (4π), we want exactly 2 vertical segments - uint16_t verticalSegments = round(input.totalAngle / TWO_PI); + // uint16_t verticalSegments = ceil(input.totalTurns); // Calculate width based on LED density per turn - float ledsPerTurn = static_cast(input.numLeds) / verticalSegments; + // float ledsPerTurn = static_cast(input.numLeds) / verticalSegments; - // Determine cylindrical dimensions - output.height = verticalSegments; - output.width = ceil(ledsPerTurn); - - output.mapping.clear(); + output->mapping.clear(); + output->width = 0; // we will change this below. + output->height = 0; // If numLeds is specified, use that for mapping size instead of grid - if (input.numLeds > 0) { - output.mapping.reserve(input.numLeds); + output->mapping.reserve(input.numLeds); + // Generate LED mapping based on numLeds + // Note that width_step should be 1.0f/float(input.numLeds) so last led in a + // turn does not wrap around. + const float width_step = + 1.0f / float(input.numLeds); // Corkscrew reaches max width on last led. + const float height_step = + 1.0f / + float(input.numLeds - 1); // Corkscrew reaches max height on last led. + // const float led_width_factor = circumferencePerTurn / TWO_PI; + const float length_per_turn = input.numLeds / input.totalTurns; - // Generate LED mapping based on numLeds - for (uint16_t i = 0; i < input.numLeds; ++i) { - // Calculate position along the corkscrew (0.0 to 1.0) - float position = static_cast(i) / (input.numLeds - 1); + for (uint16_t i = 0; i < input.numLeds; ++i) { + // Calculate position along the corkscrew (0.0 to 1.0) + const float i_f = static_cast(i); + const float alpha_width = i_f * width_step; + const float alpha_height = i_f * height_step; + const float width_before_mod = alpha_width * input.totalLength; + const float height = alpha_height * input.totalHeight; + const float width = fmodf(width_before_mod, length_per_turn); + output->mapping.push_back({width, height}); + } - // Calculate angle and height - float angle = position * input.totalAngle; - float height = position * verticalSegments; - - // Calculate circumference position - float circumference = fmodf(angle * circumferencePerTurn / TWO_PI, - circumferencePerTurn); - - // Store the mapping - output.mapping.push_back({circumference, height}); - } - } else { - // Original grid-based mapping - output.mapping.reserve(output.width * output.height); - - // Corrected super sampling step size - float thetaStep = 0.5f / output.width; - float hStep = 0.5f / output.height; - - // Precompute angle per segment - float anglePerSegment = input.totalAngle / verticalSegments; - - // Loop over cylindrical pixels - for (uint16_t h = 0; h < output.height; ++h) { - float segmentOffset = input.offsetCircumference * h; - for (uint16_t w = 0; w < output.width; ++w) { - vec2f sample = {0, 0}; - // 2x2 supersampling - for (uint8_t ssH = 0; ssH < 2; ++ssH) { - for (uint8_t ssW = 0; ssW < 2; ++ssW) { - float theta = - (w + 0.5f + ssW * thetaStep) / output.width; - float height = (h + 0.5f + ssH * hStep) / output.height; - - // Corkscrew projection (θ,h) - float corkscrewTheta = - theta * TWO_PI + anglePerSegment * h; - float corkscrewH = height * verticalSegments; - - // Apply circumference offset - float corkscrewCircumference = fmodf( - corkscrewTheta * circumferencePerTurn / TWO_PI + - segmentOffset, - circumferencePerTurn); - - // Accumulate samples - sample.x += corkscrewCircumference; - sample.y += corkscrewH; - } - } - - // Average the supersampled points - sample.x *= 0.25f; - sample.y *= 0.25f; - - output.mapping.push_back(sample); - } + if (!output->mapping.empty()) { + float max_width = 0.0f; + float max_height = 0.0f; + for (const auto &point : output->mapping) { + max_width = MAX(max_width, point.x); + max_height = MAX(max_height, point.y); } + output->width = static_cast(ceilf(max_width)) + 1; + output->height = static_cast(ceilf(max_height)) + 1; } // Apply inversion if requested if (input.invert) { - fl::reverse(output.mapping.begin(), output.mapping.end()); + fl::reverse(output->mapping.begin(), output->mapping.end()); } } - Corkscrew::Corkscrew(const Corkscrew::Input &input) : mInput(input) { - fl::generateMap(mInput, mOutput); + fl::generateState(mInput, &mState); } -vec2f Corkscrew::at(uint16_t i) const { - if (i >= mOutput.mapping.size()) { +vec2f Corkscrew::at_exact(uint16_t i) const { + if (i >= mState.mapping.size()) { // Handle out-of-bounds access, possibly by returning a default value return vec2f(0, 0); } // Convert the float position to integer - const vec2f &position = mOutput.mapping[i]; + const vec2f &position = mState.mapping[i]; return position; } -Tile2x2_u8 Corkscrew::at_splat(uint16_t i) const { - if (i >= mOutput.mapping.size()) { + +Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const { + // To finish this, we need to handle wrap around. + // To accomplish this we need a different data structure than the the + // Tile2x2_u8. + // 1. It will be called CorkscrewTile2x2_u8. + // 2. The four alpha values will each contain the index the LED is at, + // uint16_t. + // 3. There will be no origin, each pixel in the tile will contain a + // uint16_t origin. This is not supposed to be a storage format, but a + // convenient pre-computed value for rendering. + if (i >= mState.mapping.size()) { // Handle out-of-bounds access, possibly by returning a default // Tile2x2_u8 + FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: " + << i << " size: " << mState.mapping.size()); return Tile2x2_u8(); } // Use the splat function to convert the vec2f to a Tile2x2_u8 - return splat(mOutput.mapping[i]); + float i_floor = floorf(i); + float i_ceil = ceilf(i); + if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) { + // If the index is the same, just return the splat of that index + return splat(mState.mapping[static_cast(i_floor)]); + } else { + // Interpolate between the two points and return the splat of the result + vec2f pos1 = mState.mapping[static_cast(i_floor)]; + vec2f pos2 = mState.mapping[static_cast(i_ceil)]; + + if (pos2.x < pos1.x) { + // If the next point is on the other side of the cylinder, we need + // to wrap it around and bring it back into the positive direction so we can construct a Tile2x2_u8 wrap with it. + pos2.x += mState.width; + } + + vec2f interpolated_pos = + pos1 * (1.0f - (i - i_floor)) + pos2 * (i - i_floor); + return splat(interpolated_pos); + } } -size_t Corkscrew::size() const { return mOutput.mapping.size(); } +size_t Corkscrew::size() const { return mState.mapping.size(); } -CorkscrewOutput Corkscrew::generateMap(const Input &input) { - CorkscrewOutput output; - fl::generateMap(input, output); +Corkscrew::State Corkscrew::generateState(const Corkscrew::Input &input) { + CorkscrewState output; + fl::generateState(input, &output); return output; } -} // namespace fl + + +Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const { + // This is a splatted pixel, but wrapped around the cylinder. + // This is useful for rendering the corkscrew in a cylindrical way. + Tile2x2_u8 tile = at_splat_extrapolate(i); + return Tile2x2_u8_wrap(tile, mState.width, mState.height); +} + +} // namespace fl \ No newline at end of file diff --git a/libraries/FastLED/src/fl/corkscrew.h b/libraries/FastLED/src/fl/corkscrew.h index afa84cf..a648fd1 100644 --- a/libraries/FastLED/src/fl/corkscrew.h +++ b/libraries/FastLED/src/fl/corkscrew.h @@ -4,15 +4,15 @@ * @file corkscrew.h * @brief Corkscrew projection utilities * - * Corkscrew projection maps from Corkscrew (θ, h) to Cylindrical cartesian (w, - * h) space, where w = one turn of the Corkscrew. The corkscrew at (0,0) will - * map to (0,0) in cylindrical space. + * You want to draw on a rectangular surface, and have it map to a GOD DAMN + * CORKSCREW! Well guess what, this is the file for you. + * + * Corkscrew projection maps from Corkscrew angle height, (θ, h) to Cylindrical + * cartesian (w, h) space, where w = one turn of the Corkscrew. The corkscrew at + * (0) will map to the first index in the cylinder map at (0, 0). The last value + * is probly not at the max pixel value at (width - 1, height - 1), but could + * be. * - * The projection: - * - Super samples cylindrical space? - * - θ is normalized to [0, 1] or mapped to [0, W-1] for grid projection - * - Uses 2x2 super sampling for better visual quality - * - Works with XYPathRenderer's "Splat Rendering" for sub-pixel rendering * * Inputs: * - Total Height of the Corkscrew in centimeters @@ -35,6 +35,7 @@ #include "fl/allocator.h" #include "fl/geometry.h" #include "fl/math_macros.h" +#include "fl/pair.h" #include "fl/tile2x2.h" #include "fl/vector.h" @@ -46,41 +47,43 @@ namespace fl { * @return The resulting cylindrical mapping. */ struct CorkscrewInput { + float totalLength = 100; // Total length of the corkscrew in centimeters, + // set to dense 144 strips. float totalHeight = 23.25; // Total height of the corkscrew in centimeters // for 144 densly wrapped up over 19 turns - float totalAngle = 19.f * 2 * PI; // Default to 19 turns - float offsetCircumference = 0; // Optional offset for gap accounting - uint16_t numLeds = 144; // Default to dense 144 leds. - bool invert = false; // If true, reverse the mapping order + float totalTurns = 19.f; // Default to 19 turns + float offsetCircumference = 0; // Optional offset for gap accounting + uint16_t numLeds = 144; // Default to dense 144 leds. + bool invert = false; // If true, reverse the mapping order CorkscrewInput() = default; - CorkscrewInput(float height, float total_angle, float offset = 0, - uint16_t leds = 144, bool invertMapping = false) - : totalHeight(height), totalAngle(total_angle), - offsetCircumference(offset), numLeds(leds), invert(invertMapping) {} + CorkscrewInput(float total_length, float height, float total_turns, + uint16_t leds, float offset = 0, + bool invertMapping = false) + : totalLength(total_length), totalHeight(height), + totalTurns(total_turns), offsetCircumference(offset), numLeds(leds), + invert(invertMapping) {} }; -struct CorkscrewOutput { +struct CorkscrewState { uint16_t width = 0; // Width of cylindrical map (circumference of one turn) uint16_t height = 0; // Height of cylindrical map (total vertical segments) fl::vector> mapping; // Full precision mapping from corkscrew to cylindrical - CorkscrewOutput() = default; + CorkscrewState() = default; class iterator { - public: + public: using value_type = vec2f; using difference_type = int32_t; - using pointer = vec2f*; - using reference = vec2f&; + using pointer = vec2f *; + using reference = vec2f &; - iterator(CorkscrewOutput* owner, size_t position) + iterator(CorkscrewState *owner, size_t position) : owner_(owner), position_(position) {} - vec2f& operator*() const { - return owner_->mapping[position_]; - } + vec2f &operator*() const { return owner_->mapping[position_]; } - iterator& operator++() { + iterator &operator++() { ++position_; return *this; } @@ -91,67 +94,82 @@ struct CorkscrewOutput { return temp; } - bool operator==(const iterator& other) const { + iterator &operator--() { + --position_; + return *this; + } + + iterator operator--(int) { + iterator temp = *this; + --position_; + return temp; + } + + bool operator==(const iterator &other) const { return position_ == other.position_; } - bool operator!=(const iterator& other) const { + bool operator!=(const iterator &other) const { return position_ != other.position_; } - private: - CorkscrewOutput* owner_; + difference_type operator-(const iterator &other) const { + return static_cast(position_) - + static_cast(other.position_); + } + + private: + CorkscrewState *owner_; size_t position_; }; - iterator begin() { - return iterator(this, 0); - } + iterator begin() { return iterator(this, 0); } - iterator end() { - return iterator(this, mapping.size()); - } - fl::Tile2x2_u8 at(int16_t x, int16_t y) const; + iterator end() { return iterator(this, mapping.size()); } }; +// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering +// a densly wrapped LED corkscrew. class Corkscrew { public: using Input = CorkscrewInput; - using Output = CorkscrewOutput; - using iterator = CorkscrewOutput::iterator; + using State = CorkscrewState; + using iterator = CorkscrewState::iterator; Corkscrew(const Input &input); Corkscrew(const Corkscrew &) = default; + Corkscrew(Corkscrew &&) = default; + + vec2f at_exact(uint16_t i) const; + + // This is the future api. + Tile2x2_u8_wrap at_wrap(float i) const; - vec2f at(uint16_t i) const; - // This is a splatted pixel. This is will look way better than - // using at(), because it uses 2x2 neighboor sampling. - Tile2x2_u8 at_splat(uint16_t i) const; size_t size() const; - iterator begin() { - return mOutput.begin(); - } + iterator begin() { return mState.begin(); } - iterator end() { - return mOutput.end(); - } + iterator end() { return mState.end(); } /// For testing - static CorkscrewOutput generateMap(const Input &input); + static State generateState(const Input &input); - Output& access() { - return mOutput; - } + State &access() { return mState; } - const Output& access() const { - return mOutput; - } + const State &access() const { return mState; } + + int16_t cylinder_width() const { return mState.width; } + int16_t cylinder_height() const { return mState.height; } private: - Input mInput; // The input parameters defining the corkscrew - CorkscrewOutput mOutput; // The resulting cylindrical mapping + // For internal use. Splats the pixel on the surface which + // extends past the width. This extended Tile2x2 is designed + // to be wrapped around with a Tile2x2_u8_wrap. + Tile2x2_u8 at_splat_extrapolate(float i) const; + + Input mInput; // The input parameters defining the corkscrew + State mState; // The resulting cylindrical mapping }; } // namespace fl diff --git a/libraries/FastLED/src/fl/grid.h b/libraries/FastLED/src/fl/grid.h index 53e615f..87c2f5e 100644 --- a/libraries/FastLED/src/fl/grid.h +++ b/libraries/FastLED/src/fl/grid.h @@ -13,19 +13,15 @@ template class Grid { Grid(uint32_t width, uint32_t height) { reset(width, height); } void reset(uint32_t width, uint32_t height) { + clear(); if (width != mWidth || height != mHeight) { mWidth = width; mHeight = height; - // Only re-allocate if the size is now bigger. - mData.reserve(width * height); - // Fill with default objects. - while (mData.size() < width * height) { - mData.push_back(T()); - } - mSlice = fl::MatrixSlice(mData.data(), width, height, 0, 0, - width - 1, height - 1); + mData.resize(width * height); + } - clear(); + mSlice = fl::MatrixSlice(mData.data(), width, height, 0, 0, + width, height); } void clear() { @@ -60,6 +56,11 @@ template class Grid { uint32_t width() const { return mWidth; } uint32_t height() const { return mHeight; } + T* data() { return mData.data(); } + const T* data() const { return mData.data(); } + + size_t size() const { return mData.size(); } + private: static T &NullValue() { static T gNull; diff --git a/libraries/FastLED/src/fl/range_access.h b/libraries/FastLED/src/fl/range_access.h new file mode 100644 index 0000000..8c54cf4 --- /dev/null +++ b/libraries/FastLED/src/fl/range_access.h @@ -0,0 +1,44 @@ +#pragma once + +#include "fl/stdint.h" + +namespace fl { + +// fl::begin for arrays +template +constexpr T* begin(T (&array)[N]) noexcept { + return array; +} + +// fl::end for arrays +template +constexpr T* end(T (&array)[N]) noexcept { + return array + N; +} + +// fl::begin for containers with begin() member function +template +constexpr auto begin(Container& c) -> decltype(c.begin()) { + return c.begin(); +} + +// fl::begin for const containers with begin() member function +template +constexpr auto begin(const Container& c) -> decltype(c.begin()) { + return c.begin(); +} + +// fl::end for containers with end() member function +template +constexpr auto end(Container& c) -> decltype(c.end()) { + return c.end(); +} + +// fl::end for const containers with end() member function +template +constexpr auto end(const Container& c) -> decltype(c.end()) { + return c.end(); +} + +} // namespace fl + diff --git a/libraries/FastLED/src/fl/singleton.h b/libraries/FastLED/src/fl/singleton.h index 69709d6..fb95984 100644 --- a/libraries/FastLED/src/fl/singleton.h +++ b/libraries/FastLED/src/fl/singleton.h @@ -9,6 +9,8 @@ namespace fl { template class Singleton { public: static T &instance() { + // We love function level singletons!! They don't get construction until first call. + // And they seem to have locks on them in most compilers. So yay. static T instance; return instance; } diff --git a/libraries/FastLED/src/fl/sketch_macros.h b/libraries/FastLED/src/fl/sketch_macros.h new file mode 100644 index 0000000..cfdf815 --- /dev/null +++ b/libraries/FastLED/src/fl/sketch_macros.h @@ -0,0 +1,7 @@ +#pragma once + +#if defined(__AVR__) || defined(ARDUINO_TEENSYLC) || defined(ARDUINO_TEENSY30) || defined(ARDUINO_TEENSY31)|| defined(STM32F1) || defined(ARDUINO_ARCH_RENESAS_UNO) || defined(ESP8266) +#define SKETCH_HAS_LOTS_OF_MEMORY 0 +#else +#define SKETCH_HAS_LOTS_OF_MEMORY 1 +#endif \ No newline at end of file diff --git a/libraries/FastLED/src/fl/tile2x2.cpp b/libraries/FastLED/src/fl/tile2x2.cpp index f126444..0f69c06 100644 --- a/libraries/FastLED/src/fl/tile2x2.cpp +++ b/libraries/FastLED/src/fl/tile2x2.cpp @@ -1,16 +1,27 @@ #include "fl/tile2x2.h" #include "crgb.h" #include "fl/draw_visitor.h" +#include "fl/math_macros.h" #include "fl/raster.h" #include "fl/raster_sparse.h" #include "fl/unused.h" #include "fl/warn.h" #include "fl/xymap.h" -using namespace fl; - namespace fl { +namespace { +static vec2i16 wrap(const vec2i16 &v, const vec2i16 &size) { + // Wrap the vector v around the size + return vec2i16(v.x % size.x, v.y % size.y); +} + +static vec2i16 wrap_x(const vec2i16 &v, const uint16_t width) { + // Wrap the x component of the vector v around the size + return vec2i16(v.x % width, v.y); +} +} // namespace + void Tile2x2_u8::Rasterize(const Slice &tiles, XYRasterU8Sparse *out_raster) { out_raster->rasterize(tiles); @@ -34,4 +45,66 @@ void Tile2x2_u8::scale(uint8_t scale) { } } +Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width) { + const vec2i16 origin = from.origin(); + at(0, 0) = {wrap_x(vec2i16(origin.x, origin.y), width), from.at(0, 0)}; + at(0, 1) = {wrap_x(vec2i16(origin.x, origin.y + 1), width), from.at(0, 1)}; + at(1, 0) = {wrap_x(vec2i16(origin.x + 1, origin.y), width), from.at(1, 0)}; + at(1, 1) = {wrap_x(vec2i16(origin.x + 1, origin.y + 1), width), + from.at(1, 1)}; +} + +Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) { + // Wrap around the edges + x = (x + 2) % 2; + y = (y + 2) % 2; + return tile[y][x]; +} + +const Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) const { + // Wrap around the edges + x = (x + 2) % 2; + y = (y + 2) % 2; + return tile[y][x]; +} + +Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width, + uint16_t height) { + const vec2i16 origin = from.origin(); + at(0, 0) = {wrap(vec2i16(origin.x, origin.y), vec2i16(width, height)), + from.at(0, 0)}; + at(0, 1) = {wrap(vec2i16(origin.x, origin.y + 1), vec2i16(width, height)), + from.at(0, 1)}; + at(1, 0) = {wrap(vec2i16(origin.x + 1, origin.y), vec2i16(width, height)), + from.at(1, 0)}; + at(1, + 1) = {wrap(vec2i16(origin.x + 1, origin.y + 1), vec2i16(width, height)), + from.at(1, 1)}; +} + +uint8_t Tile2x2_u8::maxValue() const { + uint8_t max = 0; + max = MAX(max, at(0, 0)); + max = MAX(max, at(0, 1)); + max = MAX(max, at(1, 0)); + max = MAX(max, at(1, 1)); + return max; +} + +Tile2x2_u8 Tile2x2_u8::MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b) { + Tile2x2_u8 result; + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + result.at(x, y) = MAX(a.at(x, y), b.at(x, y)); + } + } + return result; +} + +rect Tile2x2_u8::bounds() const { + vec2 min = mOrigin; + vec2 max = mOrigin + vec2(2, 2); + return rect(min, max); +} + } // namespace fl diff --git a/libraries/FastLED/src/fl/tile2x2.h b/libraries/FastLED/src/fl/tile2x2.h index 98b1bda..6e46034 100644 --- a/libraries/FastLED/src/fl/tile2x2.h +++ b/libraries/FastLED/src/fl/tile2x2.h @@ -5,6 +5,7 @@ #include "fl/geometry.h" #include "fl/namespace.h" +#include "fl/pair.h" #include "fl/slice.h" #include "fl/xymap.h" @@ -31,6 +32,8 @@ class Tile2x2_u8 { void scale(uint8_t scale); + void setOrigin(int16_t x, int16_t y) { mOrigin = vec2(x, y); } + uint8_t &operator()(int x, int y) { return at(x, y); } uint8_t &at(int x, int y) { return mTile[y][x]; } const uint8_t &at(int x, int y) const { return mTile[y][x]; } @@ -40,33 +43,19 @@ class Tile2x2_u8 { uint8_t &lower_right() { return at(1, 0); } uint8_t &upper_right() { return at(1, 1); } - uint8_t maxValue() const { - uint8_t max = 0; - max = MAX(max, at(0, 0)); - max = MAX(max, at(0, 1)); - max = MAX(max, at(1, 0)); - max = MAX(max, at(1, 1)); - return max; - } + const uint8_t &lower_left() const { return at(0, 0); } + const uint8_t &upper_left() const { return at(0, 1); } + const uint8_t &lower_right() const { return at(1, 0); } + const uint8_t &upper_right() const { return at(1, 1); } - static Tile2x2_u8 Max(const Tile2x2_u8 &a, const Tile2x2_u8 &b) { - Tile2x2_u8 result; - for (int x = 0; x < 2; ++x) { - for (int y = 0; y < 2; ++y) { - result.at(x, y) = MAX(a.at(x, y), b.at(x, y)); - } - } - return result; - } + uint8_t maxValue() const; + + static Tile2x2_u8 MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b); vec2 origin() const { return mOrigin; } /// bounds => [begin_x, end_x) (where end_x is exclusive) - rect bounds() const { - vec2 min = mOrigin; - vec2 max = mOrigin + vec2(2, 2); - return rect(min, max); - } + rect bounds() const; // Draws the subpixel tile to the led array. void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const; @@ -96,4 +85,24 @@ class Tile2x2_u8 { vec2 mOrigin; }; +class Tile2x2_u8_wrap { + // This is a class that is like a Tile2x2_u8 but wraps around the edges. + // This is useful for cylinder mapping where the x-coordinate wraps around + // the width of the cylinder and the y-coordinate wraps around the height. + // This converts a tile2x2 to a wrapped x,y version. + public: + using Data = fl::pair; // absolute position, alpha + + Tile2x2_u8_wrap() = default; + Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width); + Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width, uint16_t height); + + // Returns the absolute position and the alpha. + Data &at(uint16_t x, uint16_t y); + const Data &at(uint16_t x, uint16_t y) const; + + private: + Data tile[2][2] = {}; // zero filled. +}; + } // namespace fl \ No newline at end of file diff --git a/libraries/FastLED/src/fl/bilinear_expansion.cpp b/libraries/FastLED/src/fl/upscale.cpp similarity index 88% rename from libraries/FastLED/src/fl/bilinear_expansion.cpp rename to libraries/FastLED/src/fl/upscale.cpp index a6d3f10..def39e8 100644 --- a/libraries/FastLED/src/fl/bilinear_expansion.cpp +++ b/libraries/FastLED/src/fl/upscale.cpp @@ -4,9 +4,9 @@ #include -#include "bilinear_expansion.h" #include "crgb.h" #include "fl/namespace.h" +#include "fl/upscale.h" #include "fl/xymap.h" namespace fl { @@ -17,9 +17,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11, uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11, uint8_t dx, uint8_t dy); -void bilinearExpandArbitrary(const CRGB *input, CRGB *output, - uint16_t inputWidth, uint16_t inputHeight, - XYMap xyMap) { +void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, XYMap xyMap) { uint16_t n = xyMap.getTotal(); uint16_t outputWidth = xyMap.getWidth(); uint16_t outputHeight = xyMap.getHeight(); @@ -82,8 +81,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11, return result; } -void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, - uint8_t inputHeight, XYMap xyMap) { +void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, + uint8_t inputHeight, XYMap xyMap) { uint8_t width = xyMap.getWidth(); uint8_t height = xyMap.getHeight(); if (width != xyMap.getWidth() || height != xyMap.getHeight()) { @@ -158,7 +157,7 @@ uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01, } // Floating-point version of bilinear interpolation -uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01, +uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11, float dx, float dy) { float dx_inv = 1.0f - dx; float dy_inv = 1.0f - dy; @@ -179,9 +178,8 @@ uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01, } // Floating-point version for arbitrary grid sizes -void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, - uint16_t inputWidth, uint16_t inputHeight, - XYMap xyMap) { +void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, XYMap xyMap) { uint16_t n = xyMap.getTotal(); uint16_t outputWidth = xyMap.getWidth(); uint16_t outputHeight = xyMap.getHeight(); @@ -214,11 +212,11 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, CRGB result; result.r = - bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy); + upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy); result.g = - bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy); + upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy); result.b = - bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy); + upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy); uint16_t idx = xyMap.mapToIndex(x, y); if (idx < n) { @@ -229,8 +227,8 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, } // Floating-point version for power-of-two grid sizes -void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, - uint8_t inputHeight, XYMap xyMap) { +void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, + uint8_t inputHeight, XYMap xyMap) { uint8_t outputWidth = xyMap.getWidth(); uint8_t outputHeight = xyMap.getHeight(); if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) { @@ -267,11 +265,11 @@ void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, CRGB result; result.r = - bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy); + upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy); result.g = - bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy); + upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy); result.b = - bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy); + upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy); uint16_t idx = xyMap.mapToIndex(x, y); if (idx < n) { diff --git a/libraries/FastLED/src/fl/upscale.h b/libraries/FastLED/src/fl/upscale.h new file mode 100644 index 0000000..3471311 --- /dev/null +++ b/libraries/FastLED/src/fl/upscale.h @@ -0,0 +1,63 @@ +/// @file bilinear_expansion.h +/// @brief Demonstrates how to mix noise generation with color palettes on a +/// 2D LED matrix + +#pragma once + +#include + +#include "crgb.h" +#include "fl/namespace.h" +#include "fl/xymap.h" + +namespace fl { + +/// @brief Performs bilinear interpolation for upscaling an image. +/// @param output The output grid to write into the interpolated values. +/// @param input The input grid to read from. +/// @param inputWidth The width of the input grid. +/// @param inputHeight The height of the input grid. +/// @param xyMap The XYMap to use to determine where to write the pixel. If the +/// pixel is mapped outside of the range then it is clipped. +void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, fl::XYMap xyMap); + +/// @brief Performs bilinear interpolation for upscaling an image. +/// @param output The output grid to write into the interpolated values. +/// @param input The input grid to read from. +/// @param inputWidth The width of the input grid. +/// @param inputHeight The height of the input grid. +/// @param xyMap The XYMap to use to determine where to write the pixel. If the +/// pixel is mapped outside of the range then it is clipped. +void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, + uint8_t inputHeight, fl::XYMap xyMap); + +// +inline void upscale(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, fl::XYMap xyMap) { + uint16_t outputWidth = xyMap.getWidth(); + uint16_t outputHeight = xyMap.getHeight(); + const bool wontFit = + (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()); + // if the input dimensions are not a power of 2 then we can't use the + // optimized version. + if (wontFit || (inputWidth & (inputWidth - 1)) || + (inputHeight & (inputHeight - 1))) { + upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap); + } else { + upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap); + } +} + +// These are here for testing purposes and are slow. Their primary use +// is to test against the fixed integer version above. +void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, + uint8_t inputHeight, fl::XYMap xyMap); + +void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth, + uint16_t inputHeight, fl::XYMap xyMap); + +uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01, + uint8_t v11, float dx, float dy); + +} // namespace fl diff --git a/libraries/FastLED/src/fl/xypath.cpp b/libraries/FastLED/src/fl/xypath.cpp index 0347edf..4435857 100644 --- a/libraries/FastLED/src/fl/xypath.cpp +++ b/libraries/FastLED/src/fl/xypath.cpp @@ -237,7 +237,7 @@ XYPathPtr XYPath::NewCatmullRomPath(uint16_t width, uint16_t height, } XYPathPtr XYPath::NewCustomPath(const fl::function &f, - const rect &drawbounds, + const rect &drawbounds, const TransformFloat &transform, const char *name) { @@ -250,7 +250,7 @@ XYPathPtr XYPath::NewCustomPath(const fl::function &f, if (!transform.is_identity()) { out->setTransform(transform); } - rect bounds; + rect bounds; if (path->hasDrawBounds(&bounds)) { if (!bounds.mMin.is_zero()) { // Set the bounds to the path's bounds diff --git a/libraries/FastLED/src/fl/xypath.h b/libraries/FastLED/src/fl/xypath.h index e6fa195..a8d47ae 100644 --- a/libraries/FastLED/src/fl/xypath.h +++ b/libraries/FastLED/src/fl/xypath.h @@ -18,8 +18,6 @@ #include "fl/transform.h" #include "fl/xypath_impls.h" -#include "fl/avr_disallowed.h" - namespace fl { class Gradient; @@ -37,7 +35,6 @@ namespace xypath_detail { fl::Str unique_missing_name(const char *prefix = "XYCustomPath: "); } // namespace xypath_detail -AVR_DISALLOWED class XYPath : public Referent { public: ///////////////////////////////////////////// @@ -57,7 +54,7 @@ class XYPath : public Referent { // Custom path using just a function. static XYPathPtr NewCustomPath(const fl::function &path, - const rect &drawbounds = rect(), + const rect &drawbounds = rect(), const TransformFloat &transform = TransformFloat(), const char *name = nullptr); @@ -148,10 +145,10 @@ class XYPathFunction : public XYPathGenerator { const Str name() const override { return mName; } void setName(const Str &name) { mName = name; } - fl::rect drawBounds() const { return mDrawBounds; } - void setDrawBounds(const fl::rect &bounds) { mDrawBounds = bounds; } + fl::rect drawBounds() const { return mDrawBounds; } + void setDrawBounds(const fl::rect &bounds) { mDrawBounds = bounds; } - bool hasDrawBounds(fl::rect *bounds) override { + bool hasDrawBounds(fl::rect *bounds) override { if (bounds) { *bounds = mDrawBounds; } @@ -161,7 +158,7 @@ class XYPathFunction : public XYPathGenerator { private: fl::function mFunction; fl::Str mName = "XYPathFunction Unnamed"; - fl::rect mDrawBounds; + fl::rect mDrawBounds; }; } // namespace fl diff --git a/libraries/FastLED/src/fl/xypath_impls.h b/libraries/FastLED/src/fl/xypath_impls.h index 3a641f5..a5c0352 100644 --- a/libraries/FastLED/src/fl/xypath_impls.h +++ b/libraries/FastLED/src/fl/xypath_impls.h @@ -57,7 +57,7 @@ class XYPathGenerator : public Referent { virtual const Str name() const = 0; virtual vec2f compute(float alpha) = 0; // No writes when returning false. - virtual bool hasDrawBounds(rect *bounds) { + virtual bool hasDrawBounds(rect *bounds) { FASTLED_UNUSED(bounds); return false; } diff --git a/libraries/FastLED/src/fx/2d/animartrix.hpp b/libraries/FastLED/src/fx/2d/animartrix.hpp index 85f7939..1106b5b 100644 --- a/libraries/FastLED/src/fx/2d/animartrix.hpp +++ b/libraries/FastLED/src/fx/2d/animartrix.hpp @@ -14,6 +14,8 @@ #include "fl/scoped_ptr.h" #include "fl/xymap.h" #include "fx/fx2d.h" +#include "eorder.h" +#include "pixel_controller.h" // For RGB_BYTE_0, RGB_BYTE_1, RGB_BYTE_2 #define ANIMARTRIX_INTERNAL #include "animartrix_detail.hpp" @@ -93,6 +95,8 @@ class Animartrix : public Fx2d { int fxGet() const { return static_cast(current_animation); } Str fxName() const override { return "Animartrix:"; } void fxNext(int fx = 1) { fxSet(fxGet() + fx); } + void setColorOrder(EOrder order) { color_order = order; } + EOrder getColorOrder() const { return color_order; } private: friend void AnimartrixLoop(Animartrix &self, uint32_t now); @@ -102,6 +106,7 @@ class Animartrix : public Fx2d { fl::scoped_ptr impl; CRGB *leds = nullptr; // Only set during draw, then unset back to nullptr. AnimartrixAnim current_animation = RGB_BLOBS5; + EOrder color_order = RGB; }; void AnimartrixLoop(Animartrix &self, uint32_t now); @@ -263,6 +268,17 @@ const char *Animartrix::getAnimationName(AnimartrixAnim animation) { void Animartrix::draw(DrawContext ctx) { this->leds = ctx.leds; AnimartrixLoop(*this, ctx.now); + if (color_order != RGB) { + for (int i = 0; i < mXyMap.getTotal(); ++i) { + CRGB &pixel = ctx.leds[i]; + const uint8_t b0_index = RGB_BYTE0(color_order); + const uint8_t b1_index = RGB_BYTE1(color_order); + const uint8_t b2_index = RGB_BYTE2(color_order); + pixel = CRGB(pixel.raw[b0_index], pixel.raw[b1_index], + pixel.raw[b2_index]); + } + + } this->leds = nullptr; } diff --git a/libraries/FastLED/src/fx/2d/scale_up.cpp b/libraries/FastLED/src/fx/2d/scale_up.cpp index ca5a011..665d582 100644 --- a/libraries/FastLED/src/fx/2d/scale_up.cpp +++ b/libraries/FastLED/src/fx/2d/scale_up.cpp @@ -3,7 +3,7 @@ #define FASTLED_INTERNAL #include "FastLED.h" -#include "fl/bilinear_expansion.h" +#include "fl/upscale.h" #include "fl/ptr.h" #include "fl/xymap.h" #include "fx/fx2d.h" @@ -64,13 +64,13 @@ void ScaleUp::draw(DrawContext context) { void ScaleUp::expand(const CRGB *input, CRGB *output, uint16_t width, uint16_t height, XYMap mXyMap) { #if FASTLED_SCALE_UP == FASTLED_SCALE_UP_ALWAYS_POWER_OF_2 - bilinearExpandPowerOf2(input, output, width, height, mXyMap); + fl::upscalePowerOf2(input, output, static_cast(width), static_cast(height), mXyMap); #elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_HIGH_PRECISION - bilinearExpandArbitrary(input, output, width, height, mXyMap); + fl::upscaleArbitrary(input, output, width, height, mXyMap); #elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_DECIDE_AT_RUNTIME - bilinearExpand(input, output, width, height, mXyMap); + fl::upscale(input, output, width, height, mXyMap); #elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_FORCE_FLOATING_POINT - bilinearExpandFloat(input, output, width, height, mXyMap); + fl::upscaleFloat(input, output, static_cast(width), static_cast(height), mXyMap); #else #error "Invalid FASTLED_SCALE_UP" #endif diff --git a/libraries/FastLED/src/fx/2d/scale_up.h b/libraries/FastLED/src/fx/2d/scale_up.h index 92539a8..706d2ce 100644 --- a/libraries/FastLED/src/fx/2d/scale_up.h +++ b/libraries/FastLED/src/fx/2d/scale_up.h @@ -9,7 +9,7 @@ #include -#include "fl/bilinear_expansion.h" +#include "fl/upscale.h" #include "fl/ptr.h" #include "fl/vector.h" #include "fl/xymap.h" diff --git a/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.cpp b/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.cpp index ccf4afb..cbc0fef 100644 --- a/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.cpp +++ b/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.cpp @@ -40,6 +40,8 @@ typedef fl::FixedVector PinList16; typedef uint8_t Pin; +bool gPsramInited = false; + // Maps multiple pins and CRGB strips to a single I2S_Esp32 object. @@ -180,6 +182,13 @@ class Driver: public InternalI2SDriver { }; InternalI2SDriver* InternalI2SDriver::create() { + if (!gPsramInited) { + gPsramInited = true; + bool ok = psramInit(); + if (!ok) { + log_e("PSRAM initialization failed, I2S driver may crash."); + } + } return new Driver(); } diff --git a/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.h b/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.h index 3d46836..0980399 100644 --- a/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.h +++ b/libraries/FastLED/src/platforms/esp/32/clockless_i2s_esp32s3.h @@ -14,6 +14,20 @@ #include "fl/vector.h" #include "eorder.h" +#ifndef FASTLED_INTERNAL +// We need to do a check for the esp-idf version because very specific versions of the +// esp-idf arduino core are broken. +#include "platforms/esp/esp_version.h" +// Broken in 3.0.2 (esp-idf 5.1.0) +// Broken in 3.0.4 (esp-idf 5.1.0) +// Broken in 3.0.7 (esp-idf 5.1.0) +// Broken in 3.1.0 (esp-idf 5.3.2) +#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 1, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +#error "I2S driver is known to not be compatible with ESP-IDF 5.1.0, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903" +#elif ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 3, 2) +#error "I2S driver is known to not be compatible with ESP-IDF 5.3.2, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903" +#endif +#endif // FASTLED_INTERNAL namespace fl { diff --git a/libraries/FastLED/src/platforms/esp/32/fastpin_esp32.h b/libraries/FastLED/src/platforms/esp/32/fastpin_esp32.h index 177a521..1ddaf3f 100644 --- a/libraries/FastLED/src/platforms/esp/32/fastpin_esp32.h +++ b/libraries/FastLED/src/platforms/esp/32/fastpin_esp32.h @@ -118,6 +118,11 @@ public: // GPIO 20-22, 24-26 used by default for SPI flash. #define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25) | _FL_BIT(26) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30)) +#elif CONFIG_IDF_TARGET_ESP32P4 +// 55 GPIO pins. ESPIDF defines all pins as valid. +// NOTE: GPIO 24 & 25 commonly used for USB and may cause flashes when uploading. +#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25)) + #elif CONFIG_IDF_TARGET_ESP32H2 // 22 GPIO pins. ESPIDF defines all pins as valid. // ESP32-H2 datasheet not yet available, when it is, mask the pins commonly used by SPI flash. @@ -131,6 +136,7 @@ public: #define FASTLED_UNUSABLE_PIN_MASK (0ULL) #endif + #endif diff --git a/libraries/FastLED/src/platforms/esp/32/rmt_5/idf5_clockless_rmt_esp32.h b/libraries/FastLED/src/platforms/esp/32/rmt_5/idf5_clockless_rmt_esp32.h index e179c7e..6a6b2d9 100644 --- a/libraries/FastLED/src/platforms/esp/32/rmt_5/idf5_clockless_rmt_esp32.h +++ b/libraries/FastLED/src/platforms/esp/32/rmt_5/idf5_clockless_rmt_esp32.h @@ -25,11 +25,7 @@ private: static RmtController5::DmaMode DefaultDmaMode() { - #ifdef FASTLED_RMT_USE_DMA - return RmtController5::DMA_ENABLED; - #else return RmtController5::DMA_AUTO; - #endif } public: diff --git a/libraries/FastLED/src/platforms/null_progmem.h b/libraries/FastLED/src/platforms/null_progmem.h index 916368c..638b5c1 100644 --- a/libraries/FastLED/src/platforms/null_progmem.h +++ b/libraries/FastLED/src/platforms/null_progmem.h @@ -1,9 +1,16 @@ #pragma once +#if !defined(PROGMEM) #define PROGMEM -#define FL_PROGMEM +#endif + +#if !defined(FL_PROGMEM) +#define FL_PROGMEM PROGMEM +#endif #define FL_PGM_READ_BYTE_NEAR(x) (*((const uint8_t *)(x))) #define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_t *)(x))) #define FL_PGM_READ_DWORD_NEAR(x) (*((const uint32_t *)(x))) -#define FL_ALIGN_PROGMEM \ No newline at end of file +#define FL_ALIGN_PROGMEM + +#define FL_PROGMEM_USES_NULL 1 \ No newline at end of file diff --git a/libraries/FastLED/src/platforms/stub/generic/led_sysdefs_generic.hpp b/libraries/FastLED/src/platforms/stub/generic/led_sysdefs_generic.hpp index ac236a8..e5dc33f 100644 --- a/libraries/FastLED/src/platforms/stub/generic/led_sysdefs_generic.hpp +++ b/libraries/FastLED/src/platforms/stub/generic/led_sysdefs_generic.hpp @@ -11,7 +11,7 @@ FL_DISABLE_WARNING_PUSH -FL_DISABLE_WARNING(global-constructors) +FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS static const auto start_time = std::chrono::system_clock::now(); diff --git a/libraries/FastLED/src/platforms/wasm/active_strip_data.h b/libraries/FastLED/src/platforms/wasm/active_strip_data.h index b2d4d3f..4095ae4 100644 --- a/libraries/FastLED/src/platforms/wasm/active_strip_data.h +++ b/libraries/FastLED/src/platforms/wasm/active_strip_data.h @@ -42,7 +42,7 @@ class ActiveStripData : public fl::EngineEvents::Listener { updateScreenMap(id, screenmap); } - const bool hasScreenMap(int id) const { return mScreenMap.has(id); } + bool hasScreenMap(int id) const { return mScreenMap.has(id); } private: friend class fl::Singleton; diff --git a/libraries/FastLED/src/platforms/wasm/active_strip_data2.h b/libraries/FastLED/src/platforms/wasm/active_strip_data2.h index 8625779..12af904 100644 --- a/libraries/FastLED/src/platforms/wasm/active_strip_data2.h +++ b/libraries/FastLED/src/platforms/wasm/active_strip_data2.h @@ -45,7 +45,7 @@ class ActiveStripData2 : public fl::EngineEvents::Listener { updateScreenMap(id, screenmap); } - const bool hasScreenMap(int id) const { return mScreenMap.has(id); } + bool hasScreenMap(int id) const { return mScreenMap.has(id); } private: friend class fl::Singleton; diff --git a/libraries/FastLED/tests/test.h b/libraries/FastLED/tests/test.h index b6e6497..9b3f360 100644 --- a/libraries/FastLED/tests/test.h +++ b/libraries/FastLED/tests/test.h @@ -17,6 +17,19 @@ #include "fl/hash_set.h" #include "fl/vector.h" +// Define an improved CHECK_CLOSE macro that provides better error messages +#define CHECK_CLOSE(a, b, epsilon) \ + do { \ + float _a = (a); \ + float _b = (b); \ + float _diff = fabsf(_a - _b); \ + bool _result = _diff <= (epsilon); \ + if (!_result) { \ + printf("CHECK_CLOSE failed: |%f - %f| = %f > %f\n", (float)_a, \ + (float)_b, _diff, (float)(epsilon)); \ + } \ + CHECK(_result); \ + } while (0) using namespace fl; @@ -80,9 +93,9 @@ namespace doctest { } }; - template - struct StringMaker> { - static String convert(const fl::vector& value) { + template + struct StringMaker> { + static String convert(const fl::vector& value) { fl::Str out; out.append(value); return out.c_str(); diff --git a/libraries/FastLED/tests/test_corkscrew.cpp b/libraries/FastLED/tests/test_corkscrew.cpp index fa6928b..6baf15e 100644 --- a/libraries/FastLED/tests/test_corkscrew.cpp +++ b/libraries/FastLED/tests/test_corkscrew.cpp @@ -2,138 +2,119 @@ #include "fl/math_macros.h" #include "test.h" +#include "fl/algorithm.h" #include "fl/sstream.h" #include "fl/corkscrew.h" - +#include "fl/grid.h" +#include "fl/tile2x2.h" // Ensure this header is included for Tile2x2_u8 #define NUM_LEDS 288 - #define TWO_PI (PI * 2.0) -// Define an improved CHECK_CLOSE macro that provides better error messages -#define CHECK_CLOSE(a, b, epsilon) \ - do { \ - float _a = (a); \ - float _b = (b); \ - float _diff = fabsf(_a - _b); \ - bool _result = _diff <= (epsilon); \ - if (!_result) { \ - printf("CHECK_CLOSE failed: |%f - %f| = %f > %f\n", (float)_a, \ - (float)_b, _diff, (float)(epsilon)); \ - } \ - CHECK(_result); \ - } while (0) - using namespace fl; -TEST_CASE("Corkscrew generateMap") { +TEST_CASE("Corkscrew Circle10 test") { Corkscrew::Input input; - input.totalHeight = 10.0f; - input.totalAngle = TWO_PI; - input.offsetCircumference = 0.0f; - input.numLeds = 10; - - Corkscrew::Output output = Corkscrew::generateMap(input); - - CHECK_EQ(output.width, 10); - CHECK_EQ(output.height, 1); // One vertical segment for one turn - CHECK_EQ(output.mapping.size(), 10); // 10 LEDs around the corkscrew - - CHECK_GE(output.mapping[0].x, 0.0f); - CHECK_LE(output.mapping[0].x, 10.0f); - CHECK_GE(output.mapping[0].y, 0.0f); - CHECK_LE(output.mapping[0].y, 1.0f); // 1 vertical segment for 2π angle -} - -TEST_CASE("Corkscrew to Frame Buffer Mapping") { - // Define the corkscrew input parameters - - - - const int kCorkscrewTotalHeight = 1; // cm - //const int CORKSCREW_WIDTH = 1; // Width of the corkscrew in pixels - //const int CORKSCREW_HEIGHT = 1; // Height of the corkscrew in pixels - const int kCorkscrewTurns = 2; // Default to 19 turns - - - - Corkscrew::Input input; - input.totalHeight = kCorkscrewTotalHeight; - input.totalAngle = kCorkscrewTurns * 2 * PI; // Default to 19 turns - input.offsetCircumference = 0.0f; - input.numLeds = 3; - - // Generate the corkscrew map - - - - Corkscrew corkscrew(input); - - volatile Corkscrew::Output* output = &corkscrew.access(); - - // vec2 first = corkscrew.at(0); - // vec2 second = corkscrew.at(1); - - Corkscrew::iterator it = corkscrew.begin(); - Corkscrew::iterator end = corkscrew.end(); - - fl::sstream ss; - ss << "\n"; - ss << "width: " << output->width << "\n"; - ss << "height: " << output->height << "\n"; - - while (it != end) { - ss << *it << "\n"; - ++it; - } - - FASTLED_WARN(ss.str()); - - - MESSAGE("done"); + input.totalLength = 10.0f; // Total length of the corkscrew in centimeters + input.totalHeight = 0.0f; + input.totalTurns = 1.0f; + input.offsetCircumference = 0.0f; // No offset + input.numLeds = 10; // Default to dense 144 LEDs times two strips + Corkscrew::State output = Corkscrew::generateState(input); + fl::vector expected_values; + expected_values.push_back(vec2f(0.0f, 0.0f)); // First LED at the bottom + expected_values.push_back(vec2f(1.0f, 0.0f)); // Second LED in the middle + expected_values.push_back(vec2f(2.0f, 0.0f)); // Third LED at the top + expected_values.push_back(vec2f(3.0f, 0.0f)); // Fourth LED at the top + expected_values.push_back(vec2f(4.0f, 0.0f)); // Fifth LED at the top + expected_values.push_back(vec2f(5.0f, 0.0f)); // Sixth LED at the top + expected_values.push_back(vec2f(6.0f, 0.0f)); // Seventh LED at the top + expected_values.push_back(vec2f(7.0f, 0.0f)); // Eighth LED at the top + expected_values.push_back(vec2f(8.0f, 0.0f)); // Ninth LED at the top + expected_values.push_back(vec2f(9.0f, 0.0f)); // Tenth LED at the top + REQUIRE_EQ(output.width, 10); + REQUIRE_EQ(output.height, 1); } -TEST_CASE("Corkscrew generateMap with two turns") { - Corkscrew::Input input; - input.totalHeight = 10.0f; - input.totalAngle = 2 * TWO_PI; // Two full turns - input.numLeds = 10; // 10 LEDs around the corkscrew - input.offsetCircumference = 0.0f; +TEST_CASE("Tile2x2_u8_wrap wrap-around test with width and height") { + // Initialize a Tile2x2_u8 with known values and set origin beyond boundaries + Tile2x2_u8 originalTile; + originalTile.setOrigin(3, 3); // Set the origin beyond the width and height + originalTile.at(0, 0) = 1; + originalTile.at(0, 1) = 2; + originalTile.at(1, 0) = 3; + originalTile.at(1, 1) = 4; - Corkscrew::Output output = Corkscrew::generateMap(input); + // Convert to Tile2x2_u8_wrap with given width and height + uint16_t width = 2; + uint16_t height = 2; + Tile2x2_u8_wrap cycTile(originalTile, width, height); - CHECK_EQ(output.width, 5); - CHECK_EQ(output.height, 2); // Two vertical segments for two turns - CHECK_EQ(output.mapping.size(), 10); // 5 width * 2 height + // Verify that the conversion wraps around correctly + REQUIRE_EQ(cycTile.at(0, 0).first.x, 1); // Wraps around to (1, 1) + REQUIRE_EQ(cycTile.at(0, 0).first.y, 1); + REQUIRE_EQ(cycTile.at(0, 1).first.x, 1); // Wraps around to (1, 0) + REQUIRE_EQ(cycTile.at(0, 1).first.y, 0); + REQUIRE_EQ(cycTile.at(1, 0).first.x, 0); // Wraps around to (0, 1) + REQUIRE_EQ(cycTile.at(1, 0).first.y, 1); + REQUIRE_EQ(cycTile.at(1, 1).first.x, 0); // Wraps around to (0, 0) + REQUIRE_EQ(cycTile.at(1, 1).first.y, 0); - // Check first pixel for correctness (basic integrity) - CHECK_GE(output.mapping[0].x, 0.0f); - CHECK_LE(output.mapping[0].x, 5.0f); - CHECK_GE(output.mapping[0].y, 0.0f); - CHECK_LE(output.mapping[0].y, 2.0f); // 2 vertical segments for 4π angle + // Verify that the values are correct + REQUIRE_EQ(cycTile.at(0, 0).second, 1); + REQUIRE_EQ(cycTile.at(0, 1).second, 2); + REQUIRE_EQ(cycTile.at(1, 0).second, 3); + REQUIRE_EQ(cycTile.at(1, 1).second, 4); } -TEST_CASE("Corkscrew circumference test") { - Corkscrew::Input input; - // Use defaults: totalHeight = 100, totalAngle = 19 * 2 * PI - input.totalHeight = 23.25f; // Total height of the corkscrew in centimeters - input.totalAngle = 19.0f * TWO_PI; // Default to 19 turns - input.offsetCircumference = 0.0f; // No offset - input.numLeds = 288; // Default to dense 144 LEDs times two strips +TEST_CASE("Tile2x2_u8_wrap conversion with width and height") { + // Initialize a Tile2x2_u8 with known values + Tile2x2_u8 originalTile; + originalTile.setOrigin(0, 0); // Set the origin to (0, 0) + originalTile.at(0, 0) = 1; + originalTile.at(0, 1) = 2; + originalTile.at(1, 0) = 3; + originalTile.at(1, 1) = 4; - Corkscrew::Output output = Corkscrew::generateMap(input); + // Convert to Tile2x2_u8_wrap with given width and height + uint16_t width = 2; + uint16_t height = 2; + Tile2x2_u8_wrap cycTile(originalTile, width, height); - // Basic sanity checks - CHECK_EQ(output.width, 16); - CHECK_EQ(output.height, 19); - CHECK_EQ(output.mapping.size(), 288); - - // Check that circumference matches calculated value - // float expectedCircumference = 100.0f / 19.0f; - // CHECK_CLOSE(output.circumference, expectedCircumference, 0.01f); + // Verify that the conversion is correct + REQUIRE_EQ(cycTile.at(0, 0).second, 1); + REQUIRE_EQ(cycTile.at(0, 1).second, 2); + REQUIRE_EQ(cycTile.at(1, 0).second, 3); + REQUIRE_EQ(cycTile.at(1, 1).second, 4); +} + +TEST_CASE("Tile2x2_u8_wrap conversion test") { + // Initialize a Tile2x2_u8 with known values and a specific origin + Tile2x2_u8 originalTile; + originalTile.setOrigin(50, 50); // Set the origin to (50, 50) + originalTile.at(0, 0) = 1; // Initialize the missing element + originalTile.at(0, 1) = 2; + originalTile.at(1, 0) = 3; + originalTile.at(1, 1) = 4; + + // Convert to Tile2x2_u8_wrap with a given width + uint16_t width = 10; + Tile2x2_u8_wrap cycTile(originalTile, width); + + // Verify that the conversion is correct + REQUIRE_EQ(cycTile.at(0, 0).second, 1); + REQUIRE_EQ(cycTile.at(0, 1).second, 2); + REQUIRE_EQ(cycTile.at(1, 0).second, 3); + REQUIRE_EQ(cycTile.at(1, 1).second, 4); + + // Verify wrap-around behavior on the x-axis + REQUIRE_EQ(cycTile.at(2, 2).second, 1); // Wraps around to (0, 0) + REQUIRE_EQ(cycTile.at(2, 3).second, 2); // Wraps around to (0, 1) + REQUIRE_EQ(cycTile.at(3, 2).second, 3); // Wraps around to (1, 0) + REQUIRE_EQ(cycTile.at(3, 3).second, 4); // Wraps around to (1, 1) } diff --git a/libraries/M5UnitUnified/library.json b/libraries/M5UnitUnified/library.json index 87d7c82..836af51 100644 --- a/libraries/M5UnitUnified/library.json +++ b/libraries/M5UnitUnified/library.json @@ -14,7 +14,7 @@ "m5stack/M5Utility": "*", "m5stack/M5HAL": "*" }, - "version": "0.1.4", + "version": "0.1.5", "frameworks": [ "arduino" ], diff --git a/libraries/M5UnitUnified/library.properties b/libraries/M5UnitUnified/library.properties index ce1af63..bd838e5 100644 --- a/libraries/M5UnitUnified/library.properties +++ b/libraries/M5UnitUnified/library.properties @@ -1,5 +1,5 @@ name=M5UnitUnified -version=0.1.4 +version=0.1.5 author=M5Stack maintainer=M5Stack sentence=M5UnitUnified is a library for unified handling of various M5 units products. (Alpha version) diff --git a/libraries/M5UnitUnified/src/googletest/test_template.hpp b/libraries/M5UnitUnified/src/googletest/test_template.hpp index dd87320..970bba5 100644 --- a/libraries/M5UnitUnified/src/googletest/test_template.hpp +++ b/libraries/M5UnitUnified/src/googletest/test_template.hpp @@ -55,7 +55,7 @@ public: /*! @class ComponentTestBase - @brief UnitComponent Derived class for testing + @brief UnitComponent Derived class for testing (I2C) @tparam U m5::unit::Component-derived classes to be tested @tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P */ @@ -109,6 +109,67 @@ protected: m5::unit::UnitUnified Units; }; +/*! + @class GPIOComponentTestBase + @brief UnitComponent Derived class for testing (GPIO) + @tparam U m5::unit::Component-derived classes to be tested + @tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P + */ +template +class GPIOComponentTestBase : public ::testing::TestWithParam { + static_assert(std::is_base_of::value, "U must be derived from Component"); + +protected: + virtual void SetUp() override + { + unit.reset(get_instance()); + if (!unit) { + FAIL() << "Failed to get_instance"; + GTEST_SKIP(); + return; + } + ustr = m5::utility::formatString("%s:%s", unit->deviceName(), is_using_hal() ? "HAL" : "GPIO"); + if (!begin()) { + FAIL() << "Failed to begin " << ustr; + GTEST_SKIP(); + } + } + + virtual void TearDown() override + { + } + + virtual bool begin() + { + auto pin_num_gpio_in = M5.getPin(m5::pin_name_t::port_b_in); + auto pin_num_gpio_out = M5.getPin(m5::pin_name_t::port_b_out); + if (pin_num_gpio_in < 0 || pin_num_gpio_out < 0) { + M5_LOGW("PortB is not available"); + Wire.end(); + pin_num_gpio_in = M5.getPin(m5::pin_name_t::port_a_pin1); + pin_num_gpio_out = M5.getPin(m5::pin_name_t::port_a_pin2); + } + M5_LOGI("getPin: %d,%d", pin_num_gpio_in, pin_num_gpio_out); + + if (is_using_hal()) { + // Using M5HAL + // TODO Not yet + return false; + } + // Using TwoWire + return Units.add(*unit, pin_num_gpio_in, pin_num_gpio_out) && Units.begin(); + } + + //!@brief Function returning true if M5HAL is used (decision based on TP) + virtual bool is_using_hal() const = 0; + //! @brief return m5::unit::Component-derived class instance (decision based on TP) + virtual U* get_instance() = 0; + + std::string ustr{}; + std::unique_ptr unit{}; + m5::unit::UnitUnified Units; +}; + } // namespace googletest } // namespace unit } // namespace m5 diff --git a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.cpp b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.cpp index 1508b59..a8f919d 100644 --- a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.cpp +++ b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.cpp @@ -462,7 +462,7 @@ m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value, } m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pulse_in(uint32_t& duration, const gpio_num_t pin, const int state, - const uint32_t timeout_us = 30000) + const uint32_t timeout_us) { duration = 0; auto start = esp_timer_get_time(); diff --git a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.hpp b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.hpp index 4d9c394..53c29bc 100644 --- a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.hpp +++ b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio.hpp @@ -87,7 +87,7 @@ public: } inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state, - const uint32_t timeout_us) override + const uint32_t timeout_us = 30000) override { return pulse_in(duration, rx_pin(), state, timeout_us); } @@ -114,7 +114,7 @@ public: return read_analog(v, tx_pin()); } inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state, - const uint32_t timeout_us) override + const uint32_t timeout_us = 30000) override { return pulse_in(duration, tx_pin(), state, timeout_us); } diff --git a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v1.cpp b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v1.cpp index 662107a..6fdb0a2 100644 --- a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v1.cpp +++ b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v1.cpp @@ -38,6 +38,13 @@ bool declrare_use_rmt_channel(const int ch) return false; } +void clear_use_rmt_channel(const int ch) +{ + if (ch >= 0 && ch < RMT_CHANNEL_MAX) { + using_rmt_channel_bits &= ~(1U << ch); + } +} + rmt_config_t to_rmt_config_tx(const adapter_config_t& cfg, const uint32_t apb_freq_hz) { rmt_config_t out{}; @@ -78,6 +85,16 @@ public: GPIOImplV1(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin) { } + virtual ~GPIOImplV1() + { + rmt_tx_stop(_tx_config.channel); + rmt_driver_uninstall(_tx_config.channel); + clear_use_rmt_channel(_tx_config.channel); + + rmt_rx_stop(_rx_config.channel); + rmt_driver_uninstall(_rx_config.channel); + clear_use_rmt_channel(_rx_config.channel); + } virtual bool begin(const gpio::adapter_config_t& cfg) override { @@ -137,8 +154,8 @@ public: } return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR; } - M5_LIB_LOGE("Failed invalid config"); + M5_LIB_LOGE("Failed invalid config"); return m5::hal::error::error_t::UNKNOWN_ERROR; } diff --git a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v2.cpp b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v2.cpp index 9e1ffd1..2f58214 100644 --- a/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v2.cpp +++ b/libraries/M5UnitUnified/src/m5_unit_component/adapter_gpio_v2.cpp @@ -48,6 +48,17 @@ public: GPIOImplV2(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin) { } + virtual ~GPIOImplV2() + { + if (_tx_handle) { + rmt_disable(_tx_handle); + rmt_del_channel(_tx_handle); + } + if (_rx_handle) { + rmt_disable(_rx_handle); + rmt_del_channel(_rx_handle); + } + } bool begin(const gpio::adapter_config_t& cfg) { @@ -88,6 +99,9 @@ public: rmt_transmit_config_t tx_config = {}; auto err = rmt_transmit(_tx_handle, copy_encoder, (gpio::m5_rmt_item_t*)data, len * sizeof(gpio::m5_rmt_item_t), &tx_config); + if (err != ESP_OK) { + M5_LIB_LOGE("Failed to transmit %d:%s", err, esp_err_to_name(err)); + } if (err == ESP_OK && waitMs) { err = rmt_tx_wait_all_done(_tx_handle, waitMs); if (err != ESP_OK) { diff --git a/libraries/M5Utility/library.json b/libraries/M5Utility/library.json index 0b32598..39f7215 100644 --- a/libraries/M5Utility/library.json +++ b/libraries/M5Utility/library.json @@ -11,7 +11,7 @@ "url": "https://github.com/m5stack/M5Utility.git" }, "dependencies": [], - "version": "0.0.2", + "version": "0.0.3", "frameworks": [ "arduino" ], diff --git a/libraries/M5Utility/library.properties b/libraries/M5Utility/library.properties index 87eb163..77fd5bf 100644 --- a/libraries/M5Utility/library.properties +++ b/libraries/M5Utility/library.properties @@ -1,5 +1,5 @@ name=M5Utility -version=0.0.2 +version=0.0.3 author=M5Stack maintainer=M5Stack sentence=Library for other M5 libraries and products diff --git a/libraries/M5Utility/platformio.ini b/libraries/M5Utility/platformio.ini index e2efe81..45da2ff 100644 --- a/libraries/M5Utility/platformio.ini +++ b/libraries/M5Utility/platformio.ini @@ -81,7 +81,7 @@ extends = m5base board = m5stack-nanoc6 platform = https://github.com/platformio/platform-espressif32.git platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.7 platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1 board_build.partitions = default.csv lib_deps = ${env.lib_deps} diff --git a/libraries/M5Utility/src/M5Utility.hpp b/libraries/M5Utility/src/M5Utility.hpp index 96c40fc..dfa52f5 100644 --- a/libraries/M5Utility/src/M5Utility.hpp +++ b/libraries/M5Utility/src/M5Utility.hpp @@ -30,6 +30,7 @@ #include "m5_utility/string.hpp" #include "m5_utility/conversion.hpp" #include "m5_utility/math.hpp" +#include "m5_utility/button_status.hpp" #include "m5_utility/misc.hpp" /*! diff --git a/libraries/M5Utility/src/m5_utility/button_status.cpp b/libraries/M5Utility/src/m5_utility/button_status.cpp new file mode 100644 index 0000000..390a5f0 --- /dev/null +++ b/libraries/M5Utility/src/m5_utility/button_status.cpp @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/*! + @file button_status.cpp + @brief Button status management + */ + +#include "button_status.hpp" + +namespace m5 { +namespace utility { +namespace button { + +void Status::setState(const uint32_t msec, const button_state_t state) +{ + if (_currentState == button_state_t::state_decide_click_count) { + _clickCount = 0; + } + + _lastMsec = msec; + bool flg_timeout = (msec - _lastClicked > _msecHold); + auto new_state = state; + switch (state) { + case button_state_t::state_nochange: + if (flg_timeout && !_press && _clickCount) { + if (_oldPress == 0 && _currentState == button_state_t::state_nochange) { + new_state = button_state_t::state_decide_click_count; + } else { + _clickCount = 0; + } + } + break; + + case button_state_t::state_clicked: + ++_clickCount; + _lastClicked = msec; + break; + + default: + break; + } + _currentState = new_state; +} + +void Status::setRawState(const uint32_t msec, const bool press) +{ + button_state_t state = button_state_t::state_nochange; + bool disable_db = (msec - _lastMsec) > _msecDebounce; + auto oldPress = _press; + _oldPress = oldPress; + if (_raw_press != press) { + _raw_press = press; + _lastRawChange = msec; + } + if (disable_db || msec - _lastRawChange >= _msecDebounce) { + if (press != (0 != oldPress)) { + _lastChange = msec; + } + + if (press) { + uint32_t holdPeriod = msec - _lastChange; + _lastHoldPeriod = holdPeriod; + if (!oldPress) { + _press = 1; + } else if (oldPress == 1 && (holdPeriod >= _msecHold)) { + _press = 2; + state = button_state_t::state_hold; + } + } else { + _press = 0; + if (oldPress == 1) { + state = button_state_t::state_clicked; + } + } + } + setState(msec, state); +} + +} // namespace button +} // namespace utility +} // namespace m5 diff --git a/libraries/M5Utility/src/m5_utility/button_status.hpp b/libraries/M5Utility/src/m5_utility/button_status.hpp new file mode 100644 index 0000000..6ba91c0 --- /dev/null +++ b/libraries/M5Utility/src/m5_utility/button_status.hpp @@ -0,0 +1,182 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/*! + @file button_status.hpp + @brief Button status management + */ +#ifndef M5_UTILITY_BUTTON_STATUS_HPP +#define M5_UTILITY_BUTTON_STATUS_HPP + +#include + +namespace m5 { +namespace utility { +namespace button { + +/*! + @class m5::utility::button::Status + @brief Button status management + @note Class compatible with Button_Class in M5Unified + */ +class Status { +public: + /*! + @enum button_state_t + @brief Button status + */ + enum class button_state_t : uint8_t { state_nochange, state_clicked, state_hold, state_decide_click_count }; + + /*! + @brief Constructor + @param hold_ms Time to be considered hold(ms) + @param debounce_ms Debounce time(ms) + */ + Status(const uint16_t hold_ms = 500, const uint16_t debounce_ms = 10) + : _msecHold{hold_ms}, _msecDebounce{debounce_ms} + { + } + + ///@name Settings + ///@{ + //! @brief Set debounce time(ms) + inline void setDebounceThreshold(const uint32_t msec) + { + _msecDebounce = msec; + } + //! @brief Set time to be considered hold(ms) + inline void setHoldThreshold(const uint32_t msec) + { + _msecHold = msec; + } + //! @brief Gets the debounce time(ms) + inline uint32_t getDebounceThreshold(void) const + { + return _msecDebounce; + } + //! @brief Gets the time to be considered hold(ms) + inline uint32_t getHoldThreshold(void) const + { + return _msecHold; + } + ///@} + + ///@name Button status + ///@{ + //! @brief Is pressed? + bool isPressed(void) const + { + return _press; + } + //! @brief Is released? + bool isReleased(void) const + { + return !_press; + } + //! @brief Returns true if the button is currently held pressed + bool isHolding(void) const + { + return _press == 2; + } + //! @brief Returns true if button was pressed + bool wasPressed(void) const + { + return !_oldPress && _press; + } + //! @brief Returns true if button was released + bool wasReleased(void) const + { + return _oldPress && !_press; + } + //! @brief Returns true when the button is pressed briefly and released + bool wasClicked(void) const + { + return _currentState == button_state_t::state_clicked; + } + //! @brief Returns true when the button has been held pressed for a while + bool wasHold(void) const + { + return _currentState == button_state_t::state_hold; + } + //! @brief Returns true when some time has passed since the button was single clicked + bool wasSingleClicked(void) const + { + return _currentState == button_state_t::state_decide_click_count && _clickCount == 1; + } + //! @brief Returns true when some time has passed since the button was double clicked + bool wasDoubleClicked(void) const + { + return _currentState == button_state_t::state_decide_click_count && _clickCount == 2; + } + //! @brief Returns true when some time has passed since the button was multiple clicked + bool wasDecideClickCount(void) const + { + return _currentState == button_state_t::state_decide_click_count; + } + //! @brief Gets the number of consecutive button clicks + uint8_t getClickCount(void) const + { + return _clickCount; + } + //! @brief Has the button press state changed? + bool wasChangePressed(void) const + { + return ((bool)_press) != ((bool)_oldPress); + } + //! @brief Pressed and released a button for more than the set hold time? + bool wasReleasedAfterHold(void) const + { + return !_press && _oldPress == 2; + } + //! @brief Was it pressed for more than the specified time? + bool wasReleaseFor(const uint32_t ms) const + { + return _oldPress && !_press && _lastHoldPeriod >= ms; + } + //! @brief Is pressed for more than the specified time? + bool pressedFor(const uint32_t ms) const + { + return (_press && _lastMsec - _lastChange >= ms); + } + //! @brief Is released for more than the specified time? + bool releasedFor(const uint32_t ms) const + { + return (!_press && _lastMsec - _lastChange >= ms); + } + ///@} + + ///@name Status + ///@{ + void setRawState(const uint32_t msec, const bool press); + void setState(const uint32_t msec, const button_state_t state); + inline button_state_t getState(void) const + { + return _currentState; + } + inline uint32_t lastChange(void) const + { + return _lastChange; + } + inline uint32_t getUpdateMsec(void) const + { + return _lastMsec; + } + ///@} + +private: + uint16_t _msecHold{500}, _msecDebounce{10}; + uint32_t _lastMsec{}, _lastChange{}, _lastRawChange{}, _lastClicked{}; + uint16_t _lastHoldPeriod{}; + button_state_t _currentState{button_state_t::state_nochange}; + bool _raw_press{}; + // 0:release 1:click 2:holding + uint8_t _press{}, _oldPress{}, _clickCount{}; +}; + +} // namespace button +} // namespace utility +} // namespace m5 + +#endif diff --git a/libraries/M5Utility/src/m5_utility/container/circular_buffer.hpp b/libraries/M5Utility/src/m5_utility/container/circular_buffer.hpp index c060230..7e971f9 100644 --- a/libraries/M5Utility/src/m5_utility/container/circular_buffer.hpp +++ b/libraries/M5Utility/src/m5_utility/container/circular_buffer.hpp @@ -1,5 +1,5 @@ /* - * Spdx-FileCopyrightText: 2024 M5Stack Technology CO LTD + * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ diff --git a/libraries/M5Utility/src/m5_utility/types.hpp b/libraries/M5Utility/src/m5_utility/types.hpp index 74c8f2d..23bc8cd 100644 --- a/libraries/M5Utility/src/m5_utility/types.hpp +++ b/libraries/M5Utility/src/m5_utility/types.hpp @@ -1,5 +1,5 @@ /* - * Spdx-Filecopyrighttext: 2024 M5Stack Technology CO LTD + * SPDX-Filecopyrighttext: 2024 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..38f7453 --- /dev/null +++ b/shell.nix @@ -0,0 +1,7 @@ +{pkgs ? import {}}: +pkgs.mkShell { + buildInputs = [ + pkgs.arduino-ide + ]; + shellHook = "arduino-ide"; +}