initial commit
This commit is contained in:
798
libraries/FastLED/src/fl/README.md
Normal file
798
libraries/FastLED/src/fl/README.md
Normal file
@@ -0,0 +1,798 @@
|
||||
# FastLED Core Library (`src/fl`)
|
||||
|
||||
This document introduces the FastLED core library housed under `src/fl/`, first by listing its headers, then by progressively expanding into an educational guide for two audiences:
|
||||
- First‑time FastLED users who want to understand what lives below `FastLED.h` and how to use common utilities
|
||||
- Experienced C++ developers exploring the `fl::` API as a cross‑platform, STL‑free foundation
|
||||
|
||||
FastLED avoids direct dependencies on the C++ standard library in embedded contexts and offers its own STL‑like building blocks in the `fl::` namespace.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview and Quick Start](#overview-and-quick-start)
|
||||
- [Header Groups (5 major areas)](#header-groups-5-major-areas)
|
||||
- [STL‑like data structures and core utilities](#1-stl-like-data-structures-and-core-utilities)
|
||||
- [Graphics, geometry, and rendering](#2-graphics-geometry-and-rendering)
|
||||
- [Color, math, and signal processing](#3-color-math-and-signal-processing)
|
||||
- [Concurrency, async, and functional](#4-concurrency-async-and-functional)
|
||||
- [I/O, JSON, and text/formatting](#5-io-json-and-textformatting)
|
||||
- [Comprehensive Module Breakdown](#comprehensive-module-breakdown)
|
||||
- [Guidance for New Users](#guidance-for-new-users)
|
||||
- [Guidance for C++ Developers](#guidance-for-c-developers)
|
||||
|
||||
---
|
||||
|
||||
## Overview and Quick Start
|
||||
|
||||
### What is `fl::`?
|
||||
|
||||
`fl::` is FastLED’s cross‑platform foundation layer. It provides containers, algorithms, memory utilities, math, graphics primitives, async/concurrency, I/O, and platform shims designed to work consistently across embedded targets and host builds. It replaces common `std::` facilities with equivalents tailored for embedded constraints and portability.
|
||||
|
||||
Key properties:
|
||||
- Cross‑platform, embedded‑friendly primitives
|
||||
- Minimal dynamic allocation where possible; clear ownership semantics
|
||||
- Consistent naming and behavior across compilers/toolchains
|
||||
- Prefer composable, header‑driven utilities
|
||||
|
||||
### Goals and design constraints
|
||||
|
||||
- Avoid fragile dependencies on `std::` in embedded builds; prefer `fl::` types
|
||||
- Emphasize deterministic behavior and low overhead
|
||||
- Provide familiar concepts (vector, span, optional, variant, function) with embedded‑aware implementations
|
||||
- Offer safe RAII ownership types and moveable wrappers for resource management
|
||||
- Keep APIs flexible by preferring non‑owning views (`fl::span`) as function parameters
|
||||
|
||||
### Naming and idioms
|
||||
|
||||
- Names live in the `fl::` namespace
|
||||
- Prefer `fl::span<T>` as input parameters over owning containers
|
||||
- Use `fl::shared_ptr<T>` / `fl::unique_ptr<T>` instead of raw pointers
|
||||
- Favor explicit ownership and lifetimes; avoid manual `new`/`delete`
|
||||
|
||||
### Getting started: common building blocks
|
||||
|
||||
Include the top‑level header you need, or just include `FastLED.h` in sketches. When writing platform‑agnostic C++ within this repo, include the specific `fl/` headers you use.
|
||||
|
||||
Common types to reach for:
|
||||
- Containers and views: `fl::vector<T>`, `fl::deque<T>`, `fl::span<T>`, `fl::slice<T>`
|
||||
- Strings and streams: `fl::string`, `fl::ostream`, `fl::sstream`, `fl::printf`
|
||||
- Optionals and variants: `fl::optional<T>`, `fl::variant<...>`
|
||||
- Memory/ownership: `fl::unique_ptr<T>`, `fl::shared_ptr<T>`, `fl::weak_ptr<T>`
|
||||
- Functional: `fl::function<Signature>`, `fl::function_list<Signature>`
|
||||
- Concurrency: `fl::thread`, `fl::mutex`, `fl::thread_local`
|
||||
- Async: `fl::promise<T>`, `fl::task`
|
||||
- Math: `fl::math`, `fl::sin32`, `fl::random`, `fl::gamma`, `fl::gradient`
|
||||
- Graphics: `fl::raster`, `fl::screenmap`, `fl::rectangular_draw_buffer`, `fl::downscale`, `fl::supersample`
|
||||
- Color: `fl::hsv`, `fl::hsv16`, `fl::colorutils`
|
||||
- JSON: `fl::Json` with safe defaults and ergonomic access
|
||||
|
||||
Example: using containers, views, and ownership
|
||||
|
||||
```cpp
|
||||
#include "fl/vector.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/memory.h" // for fl::make_unique
|
||||
|
||||
void process(fl::span<const int> values) {
|
||||
// Non-owning view over contiguous data
|
||||
}
|
||||
|
||||
void example() {
|
||||
fl::vector<int> data;
|
||||
data.push_back(1);
|
||||
data.push_back(2);
|
||||
data.push_back(3);
|
||||
|
||||
process(fl::span<const int>(data));
|
||||
|
||||
auto ptr = fl::make_unique<int>(42);
|
||||
// Exclusive ownership; automatically freed when leaving scope
|
||||
}
|
||||
```
|
||||
|
||||
Example: JSON with safe default access
|
||||
|
||||
```cpp
|
||||
#include "fl/json.h"
|
||||
|
||||
void json_example(const fl::string& jsonStr) {
|
||||
fl::Json json = fl::Json::parse(jsonStr);
|
||||
int brightness = json["config"]["brightness"] | 128; // default if missing
|
||||
bool enabled = json["enabled"] | false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Header Groups (5 major areas)
|
||||
|
||||
### 1) STL‑like data structures and core utilities
|
||||
|
||||
Containers, views, algorithms, compile‑time utilities, memory/ownership, portability helpers.
|
||||
|
||||
- Containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h`, `bitset.h`, `bitset_dynamic.h`
|
||||
- Views and ranges: `span.h`, `slice.h`, `range_access.h`
|
||||
- Tuples and algebraic types: `tuple.h`, `pair.h`, `optional.h`, `variant.h`
|
||||
- Algorithms and helpers: `algorithm.h`, `transform.h`, `comparators.h`, `range_access.h`
|
||||
- Types and traits: `types.h`, `type_traits.h`, `initializer_list.h`, `utility.h`, `move.h`, `template_magic.h`, `stdint.h`, `cstddef.h`, `namespace.h`
|
||||
- Memory/ownership: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `ptr.h`, `ptr_impl.h`, `referent.h`, `allocator.h`, `memory.h`, `memfill.h`, `inplacenew.h`
|
||||
- Portability and compiler control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h`, `deprecated.h`, `avr_disallowed.h`, `bit_cast.h`, `id_tracker.h`, `insert_result.h`, `singleton.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `vector.h`: Dynamically sized contiguous container with embedded‑friendly API.
|
||||
- `deque.h`: Double‑ended queue for efficient front/back operations.
|
||||
- `queue.h`: FIFO adapter providing push/pop semantics over an underlying container.
|
||||
- `priority_queue.h`: Heap‑based ordered queue for highest‑priority retrieval.
|
||||
- `set.h`: Ordered unique collection with deterministic iteration.
|
||||
- `map.h`: Ordered key‑value associative container.
|
||||
- `unordered_set.h`: Hash‑based unique set for average O(1) lookups.
|
||||
- `hash_map.h`: Hash‑based key‑value container tuned for embedded use.
|
||||
- `hash_set.h`: Hash set implementation complementing `hash_map.h`.
|
||||
- `rbtree.h`: Balanced tree primitive used by ordered containers.
|
||||
- `bitset.h`: Fixed‑size compile‑time bitset operations.
|
||||
- `bitset_dynamic.h`: Runtime‑sized bitset for flexible masks.
|
||||
- `span.h`: Non‑owning view over contiguous memory (preferred function parameter).
|
||||
- `slice.h`: Strided or sub‑range view utilities for buffers.
|
||||
- `range_access.h`: Helpers to unify begin/end access over custom ranges.
|
||||
- `tuple.h`: Heterogeneous fixed‑size aggregate with structured access.
|
||||
- `pair.h`: Two‑value aggregate type for simple key/value or coordinate pairs.
|
||||
- `optional.h`: Presence/absence wrapper to avoid sentinel values.
|
||||
- `variant.h`: Type‑safe tagged union for sum types without heap allocation.
|
||||
- `algorithm.h`: Core algorithms (search, sort helpers, transforms) adapted to `fl::` containers.
|
||||
- `transform.h`: Functional style element‑wise transformations with spans/ranges.
|
||||
- `comparators.h`: Reusable comparator utilities for ordering operations.
|
||||
- `types.h`: Canonical type aliases and shared type definitions.
|
||||
- `type_traits.h`: Compile‑time type inspection and enable_if‑style utilities.
|
||||
- `initializer_list.h`: Lightweight initializer list support for container construction.
|
||||
- `utility.h`: Miscellaneous helpers (swap, forward, etc.) suitable for embedded builds.
|
||||
- `move.h`: Move/forward utilities mirroring standard semantics.
|
||||
- `template_magic.h`: Metaprogramming helpers to simplify template code.
|
||||
- `stdint.h`: Fixed‑width integer definitions for cross‑compiler consistency.
|
||||
- `cstddef.h`: Size/ptrdiff and nullptr utilities for portability.
|
||||
- `namespace.h`: Internal macros/utilities for managing `fl::` namespaces safely.
|
||||
- `unique_ptr.h`: Exclusive ownership smart pointer with RAII semantics.
|
||||
- `shared_ptr.h`: Reference‑counted shared ownership smart pointer.
|
||||
- `weak_ptr.h`: Non‑owning reference to `shared_ptr`‑managed objects.
|
||||
- `scoped_ptr.h`: Scope‑bound ownership (no move) for simple RAII cleanup.
|
||||
- `scoped_array.h`: RAII wrapper for array allocations.
|
||||
- `ptr.h`/`ptr_impl.h`: Pointer abstractions and shared machinery for smart pointers.
|
||||
- `referent.h`: Base support for referent/observer relationships.
|
||||
- `allocator.h`: Custom allocators tailored for embedded constraints.
|
||||
- `memory.h`: Low‑level memory helpers (construct/destroy, address utilities).
|
||||
- `memfill.h`: Zero‑cost fill utilities (prefer over `memset` in codebase).
|
||||
- `inplacenew.h`: Placement new helpers for manual lifetime management.
|
||||
- `compiler_control.h`: Unified compiler warning/pragma control macros.
|
||||
- `force_inline.h`: Portable always‑inline control macros.
|
||||
- `virtual_if_not_avr.h`: Virtual specifier abstraction for AVR compatibility.
|
||||
- `has_define.h`: Preprocessor feature checks and conditional compilation helpers.
|
||||
- `register.h`: Register annotation shims for portability.
|
||||
- `warn.h`/`trace.h`/`dbg.h`: Logging, tracing, and diagnostics helpers.
|
||||
- `assert.h`: Assertions suited for embedded/testing builds.
|
||||
- `unused.h`: Intentional unused variable/function annotations.
|
||||
- `export.h`/`dll.h`: Visibility/export macros for shared library boundaries.
|
||||
- `deprecated.h`: Cross‑compiler deprecation annotations.
|
||||
- `avr_disallowed.h`: Guardrails to prevent unsupported usage on AVR.
|
||||
- `bit_cast.h`: Safe bit reinterpretation where supported, with fallbacks.
|
||||
- `id_tracker.h`: ID generation/tracking utility for object registries.
|
||||
- `insert_result.h`: Standardized result type for associative container inserts.
|
||||
- `singleton.h`: Simple singleton helper for cases requiring global access.
|
||||
|
||||
### 2) Graphics, geometry, and rendering
|
||||
|
||||
Rasterization, coordinate mappings, paths, grids, resampling, draw buffers, and related glue.
|
||||
|
||||
- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h`
|
||||
- Screen and tiles: `screenmap.h`, `tile2x2.h`
|
||||
- Coordinates and mappings: `xmap.h`, `xymap.h`, `screenmap.h`
|
||||
- Paths and traversal: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h`, `grid.h`, `line_simplification.h`
|
||||
- Geometry primitives: `geometry.h`, `point.h`
|
||||
- Resampling/scaling: `downscale.h`, `upscale.h`, `supersample.h`
|
||||
- Glue and UI: `leds.h`, `ui.h`, `ui_impl.h`, `rectangular_draw_buffer.h`
|
||||
- Specialized: `corkscrew.h`, `wave_simulation.h`, `wave_simulation_real.h`, `tile2x2.h`, `screenmap.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `raster.h`: Core raster interface and operations for pixel buffers.
|
||||
- `raster_sparse.h`: Sparse/partial raster representation for memory‑efficient updates.
|
||||
- `rectangular_draw_buffer.h`: Double‑buffered rectangular draw surface helpers.
|
||||
- `screenmap.h`: Maps logical coordinates to physical LED indices/layouts.
|
||||
- `tile2x2.h`: Simple 2×2 tiling utilities for composing larger surfaces.
|
||||
- `xmap.h`: General coordinate mapping utilities.
|
||||
- `xymap.h`: XY coordinate to index mapping helpers for matrices and panels.
|
||||
- `xypath.h`: Path representation in XY space for drawing and effects.
|
||||
- `xypath_impls.h`: Implementations and algorithms supporting `xypath.h`.
|
||||
- `xypath_renderer.h`: Renders paths into rasters with configurable styles.
|
||||
- `traverse_grid.h`: Grid traversal algorithms for curves/lines/fills.
|
||||
- `grid.h`: Grid data structure and iteration helpers.
|
||||
- `line_simplification.h`: Path simplification (e.g., Douglas‑Peucker‑style) for fewer segments.
|
||||
- `geometry.h`: Basic geometric computations (distances, intersections, etc.).
|
||||
- `point.h`: Small coordinate/vector primitive type.
|
||||
- `downscale.h`: Resampling utilities to reduce resolution while preserving features.
|
||||
- `upscale.h`: Upsampling utilities for enlarging frames.
|
||||
- `supersample.h`: Anti‑aliasing via multi‑sample accumulation.
|
||||
- `leds.h`: Integration helpers bridging LED buffers to rendering utilities.
|
||||
- `ui.h` / `ui_impl.h`: Minimal UI adapter hooks for demos/tests.
|
||||
- `corkscrew.h`: Experimental path/trajectory utilities for visual effects.
|
||||
- `wave_simulation.h` / `wave_simulation_real.h`: Simulated wave dynamics for organic effects.
|
||||
|
||||
### 3) Color, math, and signal processing
|
||||
|
||||
Color models, gradients, gamma, math helpers, random, noise, mapping, and basic DSP.
|
||||
|
||||
- Color and palettes: `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `gradient.h`, `fill.h`, `five_bit_hd_gamma.h`, `gamma.h`
|
||||
- Math and mapping: `math.h`, `math_macros.h`, `sin32.h`, `map_range.h`, `random.h`, `lut.h`, `clamp.h`, `clear.h`, `splat.h`, `transform.h`
|
||||
- Noise and waves: `noise_woryley.h`, `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- DSP and audio: `fft.h`, `fft_impl.h`, `audio.h`, `audio_reactive.h`
|
||||
- Time utilities: `time.h`, `time_alpha.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `colorutils.h`: High‑level color operations (blend, scale, lerp) for LED pixels.
|
||||
- `colorutils_misc.h`: Additional helpers and niche color operations.
|
||||
- `hsv.h` / `hsv16.h`: HSV color types and conversions (8‑bit and 16‑bit variants).
|
||||
- `gradient.h`: Gradient construction, sampling, and palette utilities.
|
||||
- `fill.h`: Efficient buffer/palette filling operations for pixel arrays.
|
||||
- `five_bit_hd_gamma.h`: Gamma correction tables tuned for high‑definition 5‑bit channels.
|
||||
- `gamma.h`: Gamma correction functions and LUT helpers.
|
||||
- `math.h` / `math_macros.h`: Core math primitives/macros for consistent numerics.
|
||||
- `sin32.h`: Fast fixed‑point sine approximations for animations.
|
||||
- `map_range.h`: Linear mapping and clamping between numeric ranges.
|
||||
- `random.h`: Pseudorandom utilities for effects and dithering.
|
||||
- `lut.h`: Lookup table helpers for precomputed transforms.
|
||||
- `clamp.h`: Bounds enforcement for numeric types.
|
||||
- `clear.h`: Clear/zero helpers for buffers with type awareness.
|
||||
- `splat.h`: Vectorized repeat/write helpers for bulk operations.
|
||||
- `transform.h`: Element transforms (listed here as it is often used for pixel ops too).
|
||||
- `noise_woryley.h`: Worley/cellular noise generation utilities.
|
||||
- `wave_simulation*.h`: Wavefield simulation (also referenced in graphics).
|
||||
- `fft.h` / `fft_impl.h`: Fast Fourier Transform interfaces and backends.
|
||||
- `audio.h`: Audio input/stream abstractions for host/platforms that support it.
|
||||
- `audio_reactive.h`: Utilities to drive visuals from audio features.
|
||||
- `time.h`: Timekeeping helpers (millis/micros abstractions when available).
|
||||
- `time_alpha.h`: Smoothed/exponential time‑based interpolation helpers.
|
||||
|
||||
### 4) Concurrency, async, and functional
|
||||
|
||||
Threads, synchronization, async primitives, eventing, and callable utilities.
|
||||
|
||||
- Threads and sync: `thread.h`, `mutex.h`, `thread_local.h`
|
||||
- Async primitives: `promise.h`, `promise_result.h`, `task.h`, `async.h`
|
||||
- Functional: `function.h`, `function_list.h`, `functional.h`
|
||||
- Events and engine hooks: `engine_events.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `thread.h`: Portable threading abstraction for supported hosts.
|
||||
- `mutex.h`: Mutual exclusion primitive compatible with `fl::thread`. Almost all platforms these are fake implementations.
|
||||
- `thread_local.h`: Thread‑local storage shim for supported compilers.
|
||||
- `promise.h`: Moveable wrapper around asynchronous result delivery.
|
||||
- `promise_result.h`: Result type accompanying promises/futures.
|
||||
- `task.h`: Lightweight async task primitive for orchestration.
|
||||
- `async.h`: Helpers for async composition and coordination.
|
||||
- `function.h`: Type‑erased callable wrapper analogous to `std::function`.
|
||||
- `function_list.h`: Multicast list of callables with simple invoke semantics.
|
||||
- `functional.h`: Adapters, binders, and predicates for composing callables.
|
||||
- `engine_events.h`: Event channel definitions for engine‑style systems.
|
||||
|
||||
### 5) I/O, JSON, and text/formatting
|
||||
|
||||
Streams, strings, formatted output, bytestreams, filesystem, JSON.
|
||||
|
||||
- Text and streams: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h`
|
||||
- JSON: `json.h`
|
||||
- Bytestreams and I/O: `bytestream.h`, `bytestreammemory.h`, `io.h`, `file_system.h`, `fetch.h`
|
||||
|
||||
Per‑header quick descriptions:
|
||||
|
||||
- `string.h` / `str.h`: String types and helpers without pulling in `std::string`.
|
||||
- `ostream.h` / `istream.h`: Output/input stream interfaces for host builds.
|
||||
- `sstream.h` / `strstream.h`: String‑backed stream buffers and helpers.
|
||||
- `printf.h`: Small, portable formatted print utilities.
|
||||
- `json.h`: Safe, ergonomic `fl::Json` API with defaulting operator (`|`).
|
||||
- `bytestream.h`: Sequential byte I/O abstraction for buffers/streams.
|
||||
- `bytestreammemory.h`: In‑memory byte stream implementation.
|
||||
- `io.h`: General I/O helpers for files/streams where available.
|
||||
- `file_system.h`: Minimal filesystem adapter for host platforms.
|
||||
- `fetch.h`: Basic fetch/request helpers for network‑capable hosts.
|
||||
## Comprehensive Module Breakdown
|
||||
|
||||
This section groups headers by domain, explains their role, and shows minimal usage snippets. Names shown are representative; see the header list above for the full inventory.
|
||||
|
||||
### Containers and Views
|
||||
|
||||
- Sequence and associative containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h`
|
||||
- Non‑owning and slicing: `span.h`, `slice.h`, `range_access.h`
|
||||
|
||||
Why: Embedded‑aware containers with predictable behavior across platforms. Prefer passing `fl::span<T>` to functions.
|
||||
|
||||
```cpp
|
||||
#include "fl/vector.h"
|
||||
#include "fl/span.h"
|
||||
|
||||
size_t count_nonzero(fl::span<const uint8_t> bytes) {
|
||||
size_t count = 0;
|
||||
for (uint8_t b : bytes) { if (b != 0) { ++count; } }
|
||||
return count;
|
||||
}
|
||||
```
|
||||
|
||||
### Strings and Streams
|
||||
|
||||
- Text types and streaming: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h`
|
||||
|
||||
Why: Consistent string/stream facilities without pulling in the standard streams.
|
||||
|
||||
```cpp
|
||||
#include "fl/string.h"
|
||||
#include "fl/sstream.h"
|
||||
|
||||
fl::string greet(const fl::string& name) {
|
||||
fl::sstream ss;
|
||||
ss << "Hello, " << name << "!";
|
||||
return ss.str();
|
||||
}
|
||||
```
|
||||
|
||||
### Memory and Ownership
|
||||
|
||||
- Smart pointers and utilities: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `allocator.h`, `memory.h`, `memfill.h`
|
||||
|
||||
Why: RAII ownership with explicit semantics. Prefer `fl::make_shared<T>()`/`fl::make_unique<T>()` patterns where available, or direct constructors provided by these headers.
|
||||
|
||||
```cpp
|
||||
#include "fl/shared_ptr.h"
|
||||
|
||||
struct Widget { int value; };
|
||||
|
||||
void ownership_example() {
|
||||
fl::shared_ptr<Widget> w(new Widget{123});
|
||||
auto w2 = w; // shared ownership
|
||||
}
|
||||
```
|
||||
|
||||
### Functional Utilities
|
||||
|
||||
- Callables and lists: `function.h`, `function_list.h`, `functional.h`
|
||||
|
||||
Why: Store callbacks and multicast them safely.
|
||||
|
||||
```cpp
|
||||
#include "fl/function_list.h"
|
||||
|
||||
void on_event(int code) { /* ... */ }
|
||||
|
||||
void register_handlers() {
|
||||
fl::function_list<void(int)> handlers;
|
||||
handlers.add(on_event);
|
||||
handlers(200); // invoke all
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrency and Async
|
||||
|
||||
- Threads and synchronization: `thread.h`, `mutex.h`, `thread_local.h`
|
||||
- Async primitives: `promise.h`, `promise_result.h`, `task.h`
|
||||
|
||||
Why: Lightweight foundations for parallel work or async orchestration where supported.
|
||||
|
||||
```cpp
|
||||
#include "fl/promise.h"
|
||||
|
||||
fl::promise<int> compute_async(); // returns a moveable wrapper around a future-like result
|
||||
```
|
||||
|
||||
### Math, Random, and DSP
|
||||
|
||||
- Core math and helpers: `math.h`, `math_macros.h`, `sin32.h`, `random.h`, `map_range.h`
|
||||
- Color math: `gamma.h`, `gradient.h`, `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `fill.h`
|
||||
- FFT and analysis: `fft.h`, `fft_impl.h`
|
||||
|
||||
Why: Efficient numeric operations for LED effects, audio reactivity, and transforms.
|
||||
|
||||
```cpp
|
||||
#include "fl/gamma.h"
|
||||
|
||||
uint8_t apply_gamma(uint8_t v) {
|
||||
return fl::gamma::correct8(v);
|
||||
}
|
||||
```
|
||||
|
||||
### Geometry and Grids
|
||||
|
||||
- Basic geometry: `point.h`, `geometry.h`
|
||||
- Grid traversal and simplification: `grid.h`, `traverse_grid.h`, `line_simplification.h`
|
||||
|
||||
Why: Building blocks for 2D/3D layouts and path operations.
|
||||
|
||||
### Graphics, Rasterization, and Resampling
|
||||
|
||||
- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h`
|
||||
- Screen mapping and tiling: `screenmap.h`, `tile2x2.h`, `xmap.h`, `xymap.h`
|
||||
- Paths and renderers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h`
|
||||
- Resampling and effects: `downscale.h`, `upscale.h`, `supersample.h`
|
||||
|
||||
Why: Efficient frame manipulation for LED matrices and coordinate spaces.
|
||||
|
||||
```cpp
|
||||
#include "fl/downscale.h"
|
||||
|
||||
// Downscale a high‑res buffer to a target raster (API varies by adapter)
|
||||
```
|
||||
|
||||
#### Graphics Deep Dive
|
||||
|
||||
This section explains how the major graphics utilities fit together and how to use them effectively for high‑quality, high‑performance rendering on LED strips, matrices, and complex shapes.
|
||||
|
||||
- **Wave simulation (1D/2D)**
|
||||
- Headers: `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- Concepts:
|
||||
- Super‑sampling for quality: choose `SuperSample` factors to run an internal high‑resolution simulation and downsample for display.
|
||||
- Consistent speed at higher quality: call `setExtraFrames(u8)` to update the simulation multiple times per frame to maintain perceived speed when super‑sampling.
|
||||
- Output accessors: `getf`, `geti16`, `getu8` return float/fixed/byte values; 2D version offers cylindrical wrapping via `setXCylindrical(true)`.
|
||||
- Tips for “faster” updates:
|
||||
- Use `setExtraFrames()` to advance multiple internal steps per visual frame without changing your outer timing.
|
||||
- Prefer `getu8(...)` when feeding color functions or gradients on constrained devices.
|
||||
|
||||
- **fl::Leds – array and mapped matrix**
|
||||
- Header: `leds.h`; mapping: `xymap.h`
|
||||
- `fl::Leds` wraps a `CRGB*` so you can treat it as:
|
||||
- A plain `CRGB*` via implicit conversion for classic FastLED APIs.
|
||||
- A 2D surface via `operator()(x, y)` that respects an `XYMap` (serpentine, line‑by‑line, LUT, or custom function).
|
||||
- Construction:
|
||||
- `Leds(CRGB* leds, u16 width, u16 height)` for quick serpentine/rectangular usage.
|
||||
- `Leds(CRGB* leds, const XYMap& xymap)` for full control (serpentine, rectangular, user function, or LUT).
|
||||
- Access and safety:
|
||||
- `at(x, y)`/`operator()(x, y)` map to the correct LED index; out‑of‑bounds is safe and returns a sentinel.
|
||||
- `operator[]` exposes row‑major access when the map is serpentine or line‑by‑line.
|
||||
|
||||
- **Matrix mapping (XYMap)**
|
||||
- Header: `xymap.h`
|
||||
- Create maps for common layouts:
|
||||
- `XYMap::constructSerpentine(w, h)` for typical pre‑wired panels.
|
||||
- `XYMap::constructRectangularGrid(w, h)` for row‑major matrices.
|
||||
- `XYMap::constructWithUserFunction(w, h, XYFunction)` for custom wiring.
|
||||
- `XYMap::constructWithLookUpTable(...)` for arbitrary wiring via LUT.
|
||||
- Utilities:
|
||||
- `mapToIndex(x, y)` maps coordinates to strip index.
|
||||
- `has(x, y)` tests bounds; `toScreenMap()` converts to a float UI mapping.
|
||||
|
||||
- **XY paths and path rendering (xypath)**
|
||||
- Headers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, helpers in `tile2x2.h`, `transform.h`.
|
||||
- Purpose: parameterized paths in
|
||||
\([0,1] \to (x,y)\) for drawing lines/curves/shapes with subpixel precision.
|
||||
- Ready‑made paths: point, line, circle, heart, Archimedean spiral, rose curves, phyllotaxis, Gielis superformula, and Catmull‑Rom splines with editable control points.
|
||||
- Rendering:
|
||||
- Subpixel sampling via `Tile2x2_u8` enables high quality on low‑res matrices.
|
||||
- Use `XYPath::drawColor` or `drawGradient` to rasterize into an `fl::Leds` surface.
|
||||
- `XYPath::rasterize` writes into a sparse raster for advanced composition.
|
||||
- Transforms and bounds:
|
||||
- `setDrawBounds(w, h)` and `setTransform(TransformFloat)` control framing and animation transforms.
|
||||
|
||||
- **Subpixel “splat” rendering (Tile2x2)**
|
||||
- Header: `tile2x2.h`
|
||||
- Concept: represent a subpixel footprint as a 2×2 tile of coverage values (u8 alphas). When a subpixel position moves between LEDs, neighboring LEDs get proportional contributions.
|
||||
- Use cases:
|
||||
- `Tile2x2_u8::draw(color, xymap, out)` to composite with color per‑pixel.
|
||||
- Custom blending: `tile.draw(xymap, visitor)` to apply your own alpha/compositing.
|
||||
- Wrapped tiles: `Tile2x2_u8_wrap` supports cylindrical wrap with interpolation for continuous effects.
|
||||
|
||||
- **Downscale**
|
||||
- Header: `downscale.h`
|
||||
- Purpose: resample from a higher‑resolution buffer to a smaller target, preserving features.
|
||||
- APIs:
|
||||
- `downscale(src, srcXY, dst, dstXY)` general case.
|
||||
- `downscaleHalf(...)` optimized 2× reduction (square or mapped) used automatically when sizes match.
|
||||
|
||||
- **Upscale**
|
||||
- Header: `upscale.h`
|
||||
- Purpose: bilinear upsampling from a low‑res buffer to a larger target.
|
||||
- APIs:
|
||||
- `upscale(input, output, inW, inH, xyMap)` auto‑selects optimized paths.
|
||||
- `upscaleRectangular` and `upscaleRectangularPowerOf2` bypass XY mapping for straight row‑major layouts.
|
||||
- Float reference versions exist for validation.
|
||||
|
||||
- **Corkscrew (cylindrical projection)**
|
||||
- Header: `corkscrew.h`
|
||||
- Goal: draw into a rectangular buffer and project onto a tightly wrapped helical/cylindrical LED layout.
|
||||
- Inputs and sizing:
|
||||
- `CorkscrewInput{ totalTurns, numLeds, Gap, invert }` defines geometry; helper `calculateWidth()/calculateHeight()` provide rectangle dimensions for buffer allocation.
|
||||
- Mapping and iteration:
|
||||
- `Corkscrew::at_exact(i)` returns the exact position for LED i; `at_wrap(float)` returns a wrapped `Tile2x2_u8_wrap` footprint for subpixel‑accurate sampling.
|
||||
- Use `toScreenMap(diameter)` to produce a `ScreenMap` for UI overlays or browser visualization.
|
||||
- Rectangular buffer integration:
|
||||
- `getBuffer()/data()` provide a lazily‑initialized rectangle; `fillBuffer/clearBuffer` manage it.
|
||||
- `readFrom(source_grid, use_multi_sampling)` projects from a high‑def source grid to the corkscrew using multi‑sampling for quality.
|
||||
|
||||
- Examples
|
||||
|
||||
1) Manual per‑frame draw (push every frame)
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/time.h"
|
||||
|
||||
// Your physical LED buffer
|
||||
constexpr uint16_t NUM_LEDS = 144;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
// Define a corkscrew geometry (e.g., 19 turns tightly wrapped)
|
||||
fl::Corkscrew::Input input(/*totalTurns=*/19.0f, /*numLeds=*/NUM_LEDS);
|
||||
fl::Corkscrew cork(input);
|
||||
|
||||
// A simple rectangular source grid to draw into (unwrapped cylinder)
|
||||
// Match the corkscrew's recommended rectangle
|
||||
const uint16_t W = cork.cylinder_width();
|
||||
const uint16_t H = cork.cylinder_height();
|
||||
fl::Grid<CRGB> rect(W, H);
|
||||
|
||||
void draw_pattern(uint32_t t_ms) {
|
||||
// Example: time‑animated gradient on the unwrapped cylinder
|
||||
for (uint16_t y = 0; y < H; ++y) {
|
||||
for (uint16_t x = 0; x < W; ++x) {
|
||||
uint8_t hue = uint8_t((x * 255u) / (W ? W : 1)) + uint8_t((t_ms / 10) & 0xFF);
|
||||
rect(x, y) = CHSV(hue, 255, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void project_to_strip() {
|
||||
// For each LED index i on the physical strip, sample the unwrapped
|
||||
// rectangle using the subpixel footprint from at_wrap(i)
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
auto tile = cork.at_wrap(float(i));
|
||||
// tile.at(u,v) gives {absolute_pos, alpha}
|
||||
// Blend the 2x2 neighborhood from the rectangular buffer
|
||||
uint16_t r = 0, g = 0, b = 0, a_sum = 0;
|
||||
for (uint16_t vy = 0; vy < 2; ++vy) {
|
||||
for (uint16_t vx = 0; vx < 2; ++vx) {
|
||||
auto entry = tile.at(vx, vy); // pair<vec2i16, u8>
|
||||
const auto pos = entry.first; // absolute cylinder coords
|
||||
const uint8_t a = entry.second; // alpha 0..255
|
||||
if (pos.x >= 0 && pos.x < int(W) && pos.y >= 0 && pos.y < int(H) && a > 0) {
|
||||
const CRGB& c = rect(uint16_t(pos.x), uint16_t(pos.y));
|
||||
r += uint16_t(c.r) * a;
|
||||
g += uint16_t(c.g) * a;
|
||||
b += uint16_t(c.b) * a;
|
||||
a_sum += a;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (a_sum == 0) {
|
||||
leds[i] = CRGB::Black;
|
||||
} else {
|
||||
leds[i].r = uint8_t(r / a_sum);
|
||||
leds[i].g = uint8_t(g / a_sum);
|
||||
leds[i].b = uint8_t(b / a_sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t t = fl::time();
|
||||
draw_pattern(t);
|
||||
project_to_strip();
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
2) Automate with task::before_frame (draw right before render)
|
||||
|
||||
Use the per‑frame task API; no direct EngineEvents binding is needed. Per‑frame tasks are scheduled via `fl::task` and integrated with the frame lifecycle by the async system.
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/task.h"
|
||||
#include "fl/async.h"
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/grid.h"
|
||||
|
||||
constexpr uint16_t NUM_LEDS = 144;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
fl::Corkscrew cork(fl::Corkscrew::Input(19.0f, NUM_LEDS));
|
||||
const uint16_t W = cork.cylinder_width();
|
||||
const uint16_t H = cork.cylinder_height();
|
||||
fl::Grid<CRGB> rect(W, H);
|
||||
|
||||
static void draw_pattern(uint32_t t_ms, fl::Grid<CRGB>& dst);
|
||||
static void project_to_strip(const fl::Corkscrew& c, const fl::Grid<CRGB>& src, CRGB* out, uint16_t n);
|
||||
|
||||
void setup() {
|
||||
FastLED.addLeds<WS2812B, 5, GRB>(leds, NUM_LEDS);
|
||||
|
||||
// Register a before_frame task that runs immediately before each render
|
||||
fl::task::before_frame().then([&](){
|
||||
uint32_t t = fl::time();
|
||||
draw_pattern(t, rect);
|
||||
project_to_strip(cork, rect, leds, NUM_LEDS);
|
||||
});
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The before_frame task is invoked automatically at the right time
|
||||
FastLED.show();
|
||||
// Optionally pump other async work
|
||||
fl::async_yield();
|
||||
}
|
||||
```
|
||||
|
||||
- The manual approach gives explicit control each frame.
|
||||
- The `task::before_frame()` approach schedules work just‑in‑time before rendering without manual event wiring. Use `task::after_frame()` for post‑render work.
|
||||
|
||||
- **High‑definition HSV16**
|
||||
- Headers: `hsv16.h`, implementation in `hsv16.cpp`
|
||||
- `fl::HSV16` stores 16‑bit H/S/V for high‑precision conversion to `CRGB` without banding; construct from `CRGB` or manually, and convert via `ToRGB()` or implicit cast.
|
||||
- Color boost:
|
||||
- `HSV16::colorBoost(EaseType saturation_function, EaseType luminance_function)` applies a saturation‑space boost similar to gamma, tuned separately for saturation and luminance to counter LED gamut/compression (e.g., WS2812).
|
||||
|
||||
- **Easing functions (accurate 8/16‑bit)**
|
||||
- Header: `ease.h`
|
||||
- Accurate ease‑in/out functions with 8‑ and 16‑bit variants for quad/cubic/sine families, plus dispatchers: `ease8(type, ...)`, `ease16(type, ...)`.
|
||||
- Use to shape animation curves, palette traversal, or time alpha outputs.
|
||||
|
||||
- **Time alpha**
|
||||
- Header: `time_alpha.h`
|
||||
- Helpers for time‑based interpolation: `time_alpha8/16/f` compute progress in a window `[start, end]`.
|
||||
- Stateful helpers:
|
||||
- `TimeRamp(rise, latch, fall)` for a full rise‑hold‑fall cycle.
|
||||
- `TimeClampedTransition(duration)` for a clamped one‑shot.
|
||||
- Use cases: envelope control for brightness, effect blending, or gating simulation energy over time.
|
||||
|
||||
### JSON Utilities
|
||||
|
||||
- Safe JSON access: `json.h`
|
||||
|
||||
Why: Ergonomic, crash‑resistant access with defaulting operator (`|`). Prefer the `fl::Json` API for new code.
|
||||
|
||||
```cpp
|
||||
fl::Json cfg = fl::Json::parse("{\"enabled\":true}");
|
||||
bool enabled = cfg["enabled"] | false;
|
||||
```
|
||||
|
||||
### I/O and Filesystem
|
||||
|
||||
- Basic I/O and streams: `io.h`, `ostream.h`, `istream.h`, `sstream.h`, `printf.h`
|
||||
- Filesystem adapters: `file_system.h`
|
||||
|
||||
Why: Cross‑platform text formatting, buffered I/O, and optional file access on host builds.
|
||||
|
||||
### Algorithms and Utilities
|
||||
|
||||
- Algorithms and transforms: `algorithm.h`, `transform.h`, `range_access.h`
|
||||
- Compile‑time utilities: `type_traits.h`, `tuple.h`, `variant.h`, `optional.h`, `utility.h`, `initializer_list.h`, `template_magic.h`, `types.h`, `stdint.h`, `namespace.h`
|
||||
- Platform shims and control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h`
|
||||
|
||||
Why: Familiar patterns with embedded‑appropriate implementations and compiler‑portable controls.
|
||||
|
||||
### Audio and Reactive Systems
|
||||
|
||||
### UI System (JSON UI)
|
||||
|
||||
FastLED includes a JSON‑driven UI layer that can expose controls (sliders, buttons, checkboxes, number fields, dropdowns, titles, descriptions, help, audio visualizers) for interactive demos and remote control.
|
||||
|
||||
- **Availability by platform**
|
||||
- **AVR and other low‑memory chipsets**: disabled by default. The UI is not compiled in on constrained targets.
|
||||
- **WASM build**: enabled. The web UI is available and connects to the running sketch.
|
||||
- **Other platforms**: can be enabled if the platform supplies a bridge. See `platforms/ui_defs.h` for the compile‑time gate and platform includes.
|
||||
|
||||
- **Key headers and switches**
|
||||
- `platforms/ui_defs.h`: controls `FASTLED_USE_JSON_UI` (defaults to 1 on WASM, 0 elsewhere).
|
||||
- `fl/ui.h`: C++ UI element classes (UISlider, UIButton, UICheckbox, UINumberField, UIDropdown, UITitle, UIDescription, UIHelp, UIAudio, UIGroup).
|
||||
- `platforms/shared/ui/json/ui_manager.h`: platform‑agnostic JSON UI manager that integrates with `EngineEvents`.
|
||||
- `platforms/shared/ui/json/readme.md`: implementation guide and JSON protocol.
|
||||
|
||||
- **Lifecycle and data flow**
|
||||
- The UI system uses an update manager (`JsonUiManager`) and is integrated with `EngineEvents`:
|
||||
- On frame lifecycle events, new UI elements are exported as JSON; inbound updates are processed after frames.
|
||||
- This enables remote control: UI state can be driven locally (browser) or by remote senders issuing JSON updates.
|
||||
- Send (sketch → UI): when components are added or changed, `JsonUiManager` emits JSON to the platform bridge.
|
||||
- Receive (UI → sketch): the platform calls `JsonUiManager::updateUiComponents(const char*)` with a JSON object; changes are applied on `onEndFrame()`.
|
||||
|
||||
- **Registering handlers to send/receive JSON**
|
||||
- Platform bridge constructs the manager with a function that forwards JSON to the UI:
|
||||
- `JsonUiManager(Callback updateJs)` where `updateJs(const char*)` transports JSON to the front‑end (WASM example uses JS bindings in `src/platforms/wasm/ui.cpp`).
|
||||
- To receive UI updates from the front‑end, call:
|
||||
- `MyPlatformUiManager::instance().updateUiComponents(json_str)` to queue changes; `onEndFrame()` applies them.
|
||||
|
||||
- **Sketch‑side usage examples**
|
||||
|
||||
Basic setup with controls and groups:
|
||||
|
||||
```cpp
|
||||
#include <FastLED.h>
|
||||
#include "fl/ui.h"
|
||||
|
||||
UISlider brightness("Brightness", 128, 0, 255);
|
||||
UICheckbox enabled("Enabled", true);
|
||||
UIButton reset("Reset");
|
||||
UIDropdown mode("Mode", {fl::string("Rainbow"), fl::string("Waves"), fl::string("Solid")});
|
||||
UITitle title("Demo Controls");
|
||||
UIDescription desc("Adjust parameters in real time");
|
||||
UIHelp help("# Help\nUse the controls to tweak the effect.");
|
||||
|
||||
void setup() {
|
||||
// Group controls visually in the UI
|
||||
UIGroup group("Main", title, desc, brightness, enabled, mode, reset, help);
|
||||
|
||||
// Callbacks are pumped via EngineEvents; they trigger when values change
|
||||
brightness.onChanged([](UISlider& s){ FastLED.setBrightness((uint8_t)s); });
|
||||
enabled.onChanged([](UICheckbox& c){ /* toggle effect */ });
|
||||
mode.onChanged([](UIDropdown& d){ /* change program by d.as_int() */ });
|
||||
reset.onClicked([](){ /* reset animation state */ });
|
||||
}
|
||||
```
|
||||
|
||||
Help component (see `examples/UITest/UITest.ino`):
|
||||
```cpp
|
||||
UIHelp helpMarkdown(
|
||||
R"(# FastLED UI\nThis area supports markdown, code blocks, and links.)");
|
||||
helpMarkdown.setGroup("Documentation");
|
||||
```
|
||||
|
||||
Audio input UI (WASM‑enabled platforms):
|
||||
```cpp
|
||||
UIAudio audio("Mic");
|
||||
void loop() {
|
||||
while (audio.hasNext()) {
|
||||
auto sample = audio.next();
|
||||
// Use sample data for visualizations
|
||||
}
|
||||
FastLED.show();
|
||||
}
|
||||
```
|
||||
|
||||
- **Platform bridge: sending and receiving JSON**
|
||||
- Sending from sketch to UI: `JsonUiManager` invokes the `updateJs(const char*)` callback with JSON representing either:
|
||||
- A JSON array of element definitions (on first export)
|
||||
- A JSON object of state updates (on changes)
|
||||
- Receiving from UI to sketch: the platform calls
|
||||
- `JsonUiManager::updateUiComponents(const char* jsonStr)` where `jsonStr` is a JSON object like:
|
||||
`{ "id_123": {"value": 200}, "id_456": {"pressed": true } }`
|
||||
- The manager defers application until the end of the current frame via `onEndFrame()` to ensure consistency.
|
||||
|
||||
- **WASM specifics**
|
||||
- WASM builds provide the web UI and JS glue (see `src/platforms/wasm/ui.cpp` and `src/platforms/wasm/compiler/modules/ui_manager.js`).
|
||||
- Element definitions may be routed directly to the browser UI manager to avoid feedback loops; updates are logged to an inspector, and inbound messages drive `updateUiComponents`.
|
||||
|
||||
- **Enabling on other platforms**
|
||||
- Implement a platform UI manager using `JsonUiManager` and provide the two weak functions the UI elements call:
|
||||
- `void addJsonUiComponent(fl::weak_ptr<JsonUiInternal>)`
|
||||
- `void removeJsonUiComponent(fl::weak_ptr<JsonUiInternal>)`
|
||||
- Forward platform UI events into `updateUiComponents(jsonStr)`.
|
||||
- Ensure `EngineEvents` are active so UI updates are exported and applied on frame boundaries.
|
||||
|
||||
- Audio adapters and analysis: `audio.h`, `audio_reactive.h`
|
||||
- Engine hooks: `engine_events.h`
|
||||
|
||||
Why: Build effects that respond to input signals.
|
||||
|
||||
### Miscellaneous and Specialized
|
||||
|
||||
- Wave simulation: `wave_simulation.h`, `wave_simulation_real.h`
|
||||
- Screen and LED glue: `leds.h`, `screenmap.h`
|
||||
- Path/visual experimentation: `corkscrew.h`
|
||||
|
||||
---
|
||||
|
||||
## Guidance for New Users
|
||||
|
||||
- If you are writing sketches: include `FastLED.h` and follow examples in `examples/`. The `fl::` headers power those features under the hood.
|
||||
- If you are extending FastLED internals or building advanced effects: prefer `fl::` containers and `fl::span` over STL equivalents to maintain portability.
|
||||
- Favor smart pointers and moveable wrappers for resource management. Avoid raw pointers and manual `delete`.
|
||||
- Use `fl::Json` for robust JSON handling with safe defaults.
|
||||
|
||||
## Guidance for C++ Developers
|
||||
|
||||
- Treat `fl::` as an embedded‑friendly STL. Many concepts mirror the standard library but are tuned for constrained targets and consistent compiler support.
|
||||
- When designing APIs, prefer non‑owning views (`fl::span<T>`) for input parameters and explicit ownership types for storage.
|
||||
- Use `compiler_control.h` macros for warning control and portability instead of raw pragmas.
|
||||
|
||||
---
|
||||
|
||||
This README will evolve alongside the codebase. If you are exploring a specific subsystem, start from the relevant headers listed above and follow includes to supporting modules.
|
||||
15
libraries/FastLED/src/fl/_readme
Normal file
15
libraries/FastLED/src/fl/_readme
Normal file
@@ -0,0 +1,15 @@
|
||||
This directory holds core functionality of FastLED.
|
||||
|
||||
Every class/struct/function in this directory must be under the
|
||||
namespace fl, except for special cases.
|
||||
|
||||
This is done because according to the the Arduino build system,
|
||||
all headers/cpp/hpp files in the root the src directory will be in global
|
||||
scope and other libraries can #include them by mistake.
|
||||
|
||||
This has happened so many times that I've just gone ahead and migrated all
|
||||
the new code to this directory, to prevent name collisions.
|
||||
|
||||
# When to put stuff in `fl` and not `fx`
|
||||
|
||||
If it's a visualizer, put it in the `fx` fodler. If it it's something to help to build a visualizer then you put it here.
|
||||
565
libraries/FastLED/src/fl/algorithm.h
Normal file
565
libraries/FastLED/src/fl/algorithm.h
Normal file
@@ -0,0 +1,565 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/random.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename Iterator>
|
||||
void reverse(Iterator first, Iterator last) {
|
||||
while ((first != last) && (first != --last)) {
|
||||
swap(*first++, *last);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
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 <typename Iterator, typename Compare>
|
||||
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 <typename Iterator>
|
||||
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 <typename Iterator, typename Compare>
|
||||
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 <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) {
|
||||
while (first1 != last1) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) {
|
||||
while (first1 != last1) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
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 <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
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 <typename Container1, typename Container2>
|
||||
bool equal_container(const Container1& c1, const Container2& c2) {
|
||||
fl::size size1 = c1.size();
|
||||
fl::size size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end());
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2, typename BinaryPredicate>
|
||||
bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) {
|
||||
fl::size size1 = c1.size();
|
||||
fl::size size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred);
|
||||
}
|
||||
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
void fill(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
*first = value;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
Iterator find(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
if (*first == value) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator find_if(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
while (first != last) {
|
||||
if (pred(*first)) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator find_if_not(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
while (first != last) {
|
||||
if (!pred(*first)) {
|
||||
return first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
Iterator remove(Iterator first, Iterator last, const T& value) {
|
||||
Iterator result = first;
|
||||
while (first != last) {
|
||||
if (!(*first == value)) {
|
||||
if (result != first) {
|
||||
*result = fl::move(*first);
|
||||
}
|
||||
++result;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename UnaryPredicate>
|
||||
Iterator remove_if(Iterator first, Iterator last, UnaryPredicate pred) {
|
||||
Iterator result = first;
|
||||
while (first != last) {
|
||||
if (!pred(*first)) {
|
||||
if (result != first) {
|
||||
*result = fl::move(*first);
|
||||
}
|
||||
++result;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Insertion sort implementation for small arrays
|
||||
template <typename Iterator, typename Compare>
|
||||
void insertion_sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) return;
|
||||
|
||||
for (Iterator i = first + 1; i != last; ++i) {
|
||||
auto value = fl::move(*i);
|
||||
Iterator j = i;
|
||||
|
||||
while (j != first && comp(value, *(j - 1))) {
|
||||
*j = fl::move(*(j - 1));
|
||||
--j;
|
||||
}
|
||||
|
||||
*j = fl::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Median-of-three pivot selection
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator median_of_three(Iterator first, Iterator middle, Iterator last, Compare comp) {
|
||||
if (comp(*middle, *first)) {
|
||||
if (comp(*last, *middle)) {
|
||||
return middle;
|
||||
} else if (comp(*last, *first)) {
|
||||
return last;
|
||||
} else {
|
||||
return first;
|
||||
}
|
||||
} else {
|
||||
if (comp(*last, *first)) {
|
||||
return first;
|
||||
} else if (comp(*last, *middle)) {
|
||||
return last;
|
||||
} else {
|
||||
return middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Partition function for quicksort
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator partition(Iterator first, Iterator last, Compare comp) {
|
||||
Iterator middle = first + (last - first) / 2;
|
||||
Iterator pivot_iter = median_of_three(first, middle, last - 1, comp);
|
||||
|
||||
// Move pivot to end
|
||||
swap(*pivot_iter, *(last - 1));
|
||||
Iterator pivot = last - 1;
|
||||
|
||||
Iterator i = first;
|
||||
for (Iterator j = first; j != pivot; ++j) {
|
||||
if (comp(*j, *pivot)) {
|
||||
swap(*i, *j);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
swap(*i, *pivot);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Heapsort implementation (fallback for deep recursion)
|
||||
template <typename Iterator, typename Compare>
|
||||
void sift_down(Iterator first, Iterator start, Iterator end, Compare comp) {
|
||||
Iterator root = start;
|
||||
|
||||
while (root - first <= (end - first - 2) / 2) {
|
||||
Iterator child = first + 2 * (root - first) + 1;
|
||||
Iterator swap_iter = root;
|
||||
|
||||
if (comp(*swap_iter, *child)) {
|
||||
swap_iter = child;
|
||||
}
|
||||
|
||||
if (child + 1 <= end && comp(*swap_iter, *(child + 1))) {
|
||||
swap_iter = child + 1;
|
||||
}
|
||||
|
||||
if (swap_iter == root) {
|
||||
return;
|
||||
} else {
|
||||
swap(*root, *swap_iter);
|
||||
root = swap_iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
void heapify(Iterator first, Iterator last, Compare comp) {
|
||||
Iterator start = first + (last - first - 2) / 2;
|
||||
|
||||
while (true) {
|
||||
sift_down(first, start, last - 1, comp);
|
||||
if (start == first) {
|
||||
break;
|
||||
}
|
||||
--start;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
void heap_sort(Iterator first, Iterator last, Compare comp) {
|
||||
heapify(first, last, comp);
|
||||
|
||||
Iterator end = last - 1;
|
||||
while (end > first) {
|
||||
swap(*end, *first);
|
||||
sift_down(first, first, end - 1, comp);
|
||||
--end;
|
||||
}
|
||||
}
|
||||
|
||||
// Quicksort implementation
|
||||
template <typename Iterator, typename Compare>
|
||||
void quicksort_impl(Iterator first, Iterator last, Compare comp) {
|
||||
if (last - first <= 16) { // Use insertion sort for small arrays
|
||||
insertion_sort(first, last, comp);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator pivot = partition(first, last, comp);
|
||||
quicksort_impl(first, pivot, comp);
|
||||
quicksort_impl(pivot + 1, last, comp);
|
||||
}
|
||||
|
||||
// Rotate elements in range [first, last) so that middle becomes the new first
|
||||
template <typename Iterator>
|
||||
void rotate_impl(Iterator first, Iterator middle, Iterator last) {
|
||||
if (first == middle || middle == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator next = middle;
|
||||
while (first != next) {
|
||||
swap(*first++, *next++);
|
||||
if (next == last) {
|
||||
next = middle;
|
||||
} else if (first == middle) {
|
||||
middle = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the position where value should be inserted in sorted range [first, last)
|
||||
template <typename Iterator, typename T, typename Compare>
|
||||
Iterator lower_bound_impl(Iterator first, Iterator last, const T& value, Compare comp) {
|
||||
auto count = last - first;
|
||||
while (count > 0) {
|
||||
auto step = count / 2;
|
||||
Iterator it = first + step;
|
||||
if (comp(*it, value)) {
|
||||
first = ++it;
|
||||
count -= step + 1;
|
||||
} else {
|
||||
count = step;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
// In-place merge operation for merge sort (stable sort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void merge_inplace(Iterator first, Iterator middle, Iterator last, Compare comp) {
|
||||
// If one of the ranges is empty, nothing to merge
|
||||
if (first == middle || middle == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If arrays are small enough, use insertion-based merge
|
||||
auto left_size = middle - first;
|
||||
auto right_size = last - middle;
|
||||
if (left_size + right_size <= 32) {
|
||||
// Simple insertion-based merge for small arrays
|
||||
Iterator left = first;
|
||||
Iterator right = middle;
|
||||
|
||||
while (left < middle && right < last) {
|
||||
if (!comp(*right, *left)) {
|
||||
// left element is in correct position
|
||||
++left;
|
||||
} else {
|
||||
// right element needs to be inserted into left part
|
||||
auto value = fl::move(*right);
|
||||
Iterator shift_end = right;
|
||||
Iterator shift_start = left;
|
||||
|
||||
// Shift elements to make room
|
||||
while (shift_end > shift_start) {
|
||||
*shift_end = fl::move(*(shift_end - 1));
|
||||
--shift_end;
|
||||
}
|
||||
|
||||
*left = fl::move(value);
|
||||
++left;
|
||||
++middle; // middle has shifted right
|
||||
++right;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For larger arrays, use rotation-based merge
|
||||
if (left_size == 0 || right_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (left_size == 1) {
|
||||
// Find insertion point for the single left element in right array
|
||||
Iterator pos = lower_bound_impl(middle, last, *first, comp);
|
||||
rotate_impl(first, middle, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (right_size == 1) {
|
||||
// Find insertion point for the single right element in left array
|
||||
Iterator pos = lower_bound_impl(first, middle, *(last - 1), comp);
|
||||
rotate_impl(pos, middle, last);
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide both arrays and recursively merge
|
||||
Iterator left_mid = first + left_size / 2;
|
||||
Iterator right_mid = lower_bound_impl(middle, last, *left_mid, comp);
|
||||
|
||||
// Rotate to bring the two middle parts together
|
||||
rotate_impl(left_mid, middle, right_mid);
|
||||
|
||||
// Update middle position
|
||||
Iterator new_middle = left_mid + (right_mid - middle);
|
||||
|
||||
// Recursively merge the two parts
|
||||
merge_inplace(first, left_mid, new_middle, comp);
|
||||
merge_inplace(new_middle, right_mid, last, comp);
|
||||
}
|
||||
|
||||
// Merge sort implementation (stable, in-place)
|
||||
template <typename Iterator, typename Compare>
|
||||
void mergesort_impl(Iterator first, Iterator last, Compare comp) {
|
||||
auto size = last - first;
|
||||
if (size <= 16) { // Use insertion sort for small arrays (it's stable)
|
||||
insertion_sort(first, last, comp);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator middle = first + size / 2;
|
||||
mergesort_impl(first, middle, comp);
|
||||
mergesort_impl(middle, last, comp);
|
||||
merge_inplace(first, middle, last, comp);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Sort function with custom comparator (using quicksort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last || first + 1 == last) {
|
||||
return; // Already sorted or empty
|
||||
}
|
||||
|
||||
detail::quicksort_impl(first, last, comp);
|
||||
}
|
||||
|
||||
// Sort function with default comparator
|
||||
template <typename Iterator>
|
||||
void sort(Iterator first, Iterator last) {
|
||||
// Use explicit template parameter to avoid C++14 auto in lambda
|
||||
typedef typename fl::remove_reference<decltype(*first)>::type value_type;
|
||||
sort(first, last, [](const value_type& a, const value_type& b) { return a < b; });
|
||||
}
|
||||
|
||||
// Stable sort function with custom comparator (using merge sort)
|
||||
template <typename Iterator, typename Compare>
|
||||
void stable_sort(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last || first + 1 == last) {
|
||||
return; // Already sorted or empty
|
||||
}
|
||||
|
||||
detail::mergesort_impl(first, last, comp);
|
||||
}
|
||||
|
||||
// Stable sort function with default comparator
|
||||
template <typename Iterator>
|
||||
void stable_sort(Iterator first, Iterator last) {
|
||||
// Use explicit template parameter to avoid C++14 auto in lambda
|
||||
typedef typename fl::remove_reference<decltype(*first)>::type value_type;
|
||||
stable_sort(first, last, [](const value_type& a, const value_type& b) { return a < b; });
|
||||
}
|
||||
|
||||
// Shuffle function with custom random generator (Fisher-Yates shuffle)
|
||||
template <typename Iterator, typename RandomGenerator>
|
||||
void shuffle(Iterator first, Iterator last, RandomGenerator& g) {
|
||||
if (first == last) {
|
||||
return; // Empty range, nothing to shuffle
|
||||
}
|
||||
|
||||
auto n = last - first;
|
||||
for (auto i = n - 1; i > 0; --i) {
|
||||
// Generate random index from 0 to i (inclusive)
|
||||
auto j = g() % (i + 1);
|
||||
|
||||
// Swap elements at positions i and j
|
||||
swap(*(first + i), *(first + j));
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle function with fl::fl_random instance
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator first, Iterator last, fl_random& rng) {
|
||||
if (first == last) {
|
||||
return; // Empty range, nothing to shuffle
|
||||
}
|
||||
|
||||
auto n = last - first;
|
||||
for (auto i = n - 1; i > 0; --i) {
|
||||
// Generate random index from 0 to i (inclusive)
|
||||
auto j = rng(static_cast<u32>(i + 1));
|
||||
|
||||
// Swap elements at positions i and j
|
||||
swap(*(first + i), *(first + j));
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle function with default random generator
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator first, Iterator last) {
|
||||
shuffle(first, last, default_random());
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
18
libraries/FastLED/src/fl/align.h
Normal file
18
libraries/FastLED/src/fl/align.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
#if defined(FASTLED_TESTING) || defined(__EMSCRIPTEN__)
|
||||
// max_align_t and alignof
|
||||
#include <cstddef> // ok include
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define FL_ALIGN_BYTES 8
|
||||
#define FL_ALIGN alignas(FL_ALIGN_BYTES)
|
||||
#define FL_ALIGN_AS(T) alignas(alignof(T))
|
||||
#else
|
||||
#define FL_ALIGN_BYTES 1
|
||||
#define FL_ALIGN
|
||||
#define FL_ALIGN_AS(T)
|
||||
#endif
|
||||
138
libraries/FastLED/src/fl/allocator.cpp
Normal file
138
libraries/FastLED/src/fl/allocator.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/thread_local.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_system.h"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef ESP32
|
||||
// On esp32, attempt to always allocate in psram first.
|
||||
void *DefaultAlloc(fl::size size) {
|
||||
void *out = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
|
||||
if (out == nullptr) {
|
||||
// Fallback to default allocator.
|
||||
out = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
void DefaultFree(void *ptr) { heap_caps_free(ptr); }
|
||||
#else
|
||||
void *DefaultAlloc(fl::size size) { return malloc(size); }
|
||||
void DefaultFree(void *ptr) { free(ptr); }
|
||||
#endif
|
||||
|
||||
void *(*Alloc)(fl::size) = DefaultAlloc;
|
||||
void (*Dealloc)(void *) = DefaultFree;
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
// Test hook interface pointer
|
||||
MallocFreeHook* gMallocFreeHook = nullptr;
|
||||
|
||||
int& tls_reintrancy_count() {
|
||||
static fl::ThreadLocal<int> enabled;
|
||||
return enabled.access();
|
||||
}
|
||||
|
||||
struct MemoryGuard {
|
||||
int& reintrancy_count;
|
||||
MemoryGuard(): reintrancy_count(tls_reintrancy_count()) {
|
||||
reintrancy_count++;
|
||||
}
|
||||
~MemoryGuard() {
|
||||
reintrancy_count--;
|
||||
}
|
||||
bool enabled() const {
|
||||
return reintrancy_count <= 1;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
void SetMallocFreeHook(MallocFreeHook* hook) {
|
||||
gMallocFreeHook = hook;
|
||||
}
|
||||
|
||||
void ClearMallocFreeHook() {
|
||||
gMallocFreeHook = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) {
|
||||
Alloc = alloc;
|
||||
Dealloc = free;
|
||||
}
|
||||
|
||||
void *PSRamAllocate(fl::size size, bool zero) {
|
||||
|
||||
void *ptr = Alloc(size);
|
||||
if (ptr && zero) {
|
||||
memset(ptr, 0, size);
|
||||
}
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onMalloc(ptr, size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void PSRamDeallocate(void *ptr) {
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
// gMallocFreeHook->onFree(ptr);
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onFree(ptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dealloc(ptr);
|
||||
}
|
||||
|
||||
void* Malloc(fl::size size) {
|
||||
void* ptr = Alloc(size);
|
||||
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onMalloc(ptr, size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Free(void *ptr) {
|
||||
#if defined(FASTLED_TESTING)
|
||||
if (gMallocFreeHook && ptr) {
|
||||
MemoryGuard allows_hook;
|
||||
if (allows_hook.enabled()) {
|
||||
gMallocFreeHook->onFree(ptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dealloc(ptr);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
774
libraries/FastLED/src/fl/allocator.h
Normal file
774
libraries/FastLED/src/fl/allocator.h
Normal file
@@ -0,0 +1,774 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/bit_cast.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/bitset.h"
|
||||
|
||||
#ifndef FASTLED_DEFAULT_SLAB_SIZE
|
||||
#define FASTLED_DEFAULT_SLAB_SIZE 8
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Test hooks for malloc/free operations
|
||||
#if defined(FASTLED_TESTING)
|
||||
// Interface class for malloc/free test hooks
|
||||
class MallocFreeHook {
|
||||
public:
|
||||
virtual ~MallocFreeHook() = default;
|
||||
virtual void onMalloc(void* ptr, fl::size size) = 0;
|
||||
virtual void onFree(void* ptr) = 0;
|
||||
};
|
||||
|
||||
// Set test hooks for malloc and free operations
|
||||
void SetMallocFreeHook(MallocFreeHook* hook);
|
||||
|
||||
// Clear test hooks (set to nullptr)
|
||||
void ClearMallocFreeHook();
|
||||
#endif
|
||||
|
||||
void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *));
|
||||
void *PSRamAllocate(fl::size size, bool zero = true);
|
||||
void PSRamDeallocate(void *ptr);
|
||||
void* Malloc(fl::size size);
|
||||
void Free(void *ptr);
|
||||
|
||||
template <typename T> class PSRamAllocator {
|
||||
public:
|
||||
static T *Alloc(fl::size n) {
|
||||
void *ptr = PSRamAllocate(sizeof(T) * n, true);
|
||||
return fl::bit_cast_ptr<T>(ptr);
|
||||
}
|
||||
|
||||
static void Free(T *p) {
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
PSRamDeallocate(p);
|
||||
}
|
||||
};
|
||||
|
||||
// std compatible allocator.
|
||||
template <typename T> class allocator {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator<U>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
template <typename U>
|
||||
allocator(const allocator<U>&) noexcept {}
|
||||
|
||||
// Destructor
|
||||
~allocator() noexcept {}
|
||||
|
||||
// Use this to allocate large blocks of memory for T.
|
||||
// This is useful for large arrays or objects that need to be allocated
|
||||
// in a single block.
|
||||
T* allocate(fl::size n) {
|
||||
if (n == 0) {
|
||||
return nullptr; // Handle zero allocation
|
||||
}
|
||||
fl::size size = sizeof(T) * n;
|
||||
void *ptr = Malloc(size);
|
||||
if (ptr == nullptr) {
|
||||
return nullptr; // Handle allocation failure
|
||||
}
|
||||
fl::memfill(ptr, 0, sizeof(T) * n); // Zero-initialize the memory
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
void deallocate(T* p, fl::size n) {
|
||||
FASTLED_UNUSED(n);
|
||||
if (p == nullptr) {
|
||||
return; // Handle null pointer
|
||||
}
|
||||
Free(p); // Free the allocated memory
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> class allocator_psram {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_psram<U>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_psram() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
template <typename U>
|
||||
allocator_psram(const allocator_psram<U>&) noexcept {}
|
||||
|
||||
// Destructor
|
||||
~allocator_psram() noexcept {}
|
||||
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
return PSRamAllocator<T>::Alloc(n);
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
PSRamAllocator<T>::Free(p);
|
||||
FASTLED_UNUSED(n);
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Slab allocator for fixed-size objects
|
||||
// Optimized for frequent allocation/deallocation of objects of the same size
|
||||
// Uses pre-allocated memory slabs with free lists to reduce fragmentation
|
||||
template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
|
||||
class SlabAllocator {
|
||||
private:
|
||||
|
||||
|
||||
static constexpr fl::size SLAB_BLOCK_SIZE = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
|
||||
static constexpr fl::size BLOCKS_PER_SLAB = SLAB_SIZE;
|
||||
static constexpr fl::size SLAB_MEMORY_SIZE = SLAB_BLOCK_SIZE * BLOCKS_PER_SLAB;
|
||||
|
||||
struct Slab {
|
||||
Slab* next;
|
||||
u8* memory;
|
||||
fl::size allocated_count;
|
||||
fl::bitset_fixed<BLOCKS_PER_SLAB> allocated_blocks; // Track which blocks are allocated
|
||||
|
||||
Slab() : next(nullptr), memory(nullptr), allocated_count(0) {}
|
||||
|
||||
~Slab() {
|
||||
if (memory) {
|
||||
free(memory);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Slab* slabs_;
|
||||
fl::size total_allocated_;
|
||||
fl::size total_deallocated_;
|
||||
|
||||
Slab* createSlab() {
|
||||
Slab* slab = static_cast<Slab*>(malloc(sizeof(Slab)));
|
||||
if (!slab) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use placement new to properly initialize the Slab
|
||||
new(slab) Slab();
|
||||
|
||||
slab->memory = static_cast<u8*>(malloc(SLAB_MEMORY_SIZE));
|
||||
if (!slab->memory) {
|
||||
slab->~Slab();
|
||||
free(slab);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize all blocks in the slab as free
|
||||
slab->allocated_blocks.reset(); // All blocks start as free
|
||||
|
||||
// Add slab to the slab list
|
||||
slab->next = slabs_;
|
||||
slabs_ = slab;
|
||||
|
||||
return slab;
|
||||
}
|
||||
|
||||
void* allocateFromSlab(fl::size n = 1) {
|
||||
// Try to find n contiguous free blocks in existing slabs
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
void* ptr = findContiguousBlocks(slab, n);
|
||||
if (ptr) {
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
// No contiguous blocks found, create new slab if n fits
|
||||
if (n <= BLOCKS_PER_SLAB) {
|
||||
if (!createSlab()) {
|
||||
return nullptr; // Out of memory
|
||||
}
|
||||
|
||||
// Try again with the new slab
|
||||
return findContiguousBlocks(slabs_, n);
|
||||
}
|
||||
|
||||
// Request too large for slab, fall back to malloc
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* findContiguousBlocks(Slab* slab, fl::size n) {
|
||||
// Check if allocation is too large for this slab
|
||||
if (n > BLOCKS_PER_SLAB) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use bitset's find_run to find n contiguous free blocks (false = free)
|
||||
fl::i32 start = slab->allocated_blocks.find_run(false, static_cast<fl::u32>(n));
|
||||
if (start >= 0) {
|
||||
// Mark blocks as allocated
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
slab->allocated_blocks.set(static_cast<fl::u32>(start + i), true);
|
||||
}
|
||||
slab->allocated_count += n;
|
||||
total_allocated_ += n;
|
||||
|
||||
// Return pointer to the first block
|
||||
return slab->memory + static_cast<fl::size>(start) * SLAB_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void deallocateToSlab(void* ptr, fl::size n = 1) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find which slab this block belongs to
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
u8* slab_start = slab->memory;
|
||||
u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
|
||||
u8* block_ptr = fl::bit_cast_ptr<u8>(ptr);
|
||||
|
||||
if (block_ptr >= slab_start && block_ptr < slab_end) {
|
||||
fl::size block_index = (block_ptr - slab_start) / SLAB_BLOCK_SIZE;
|
||||
|
||||
// Mark blocks as free in the bitset
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (block_index + i < BLOCKS_PER_SLAB) {
|
||||
slab->allocated_blocks.set(block_index + i, false);
|
||||
}
|
||||
}
|
||||
|
||||
slab->allocated_count -= n;
|
||||
total_deallocated_ += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SlabAllocator() : slabs_(nullptr), total_allocated_(0), total_deallocated_(0) {}
|
||||
|
||||
// Destructor
|
||||
~SlabAllocator() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// Non-copyable
|
||||
SlabAllocator(const SlabAllocator&) = delete;
|
||||
SlabAllocator& operator=(const SlabAllocator&) = delete;
|
||||
|
||||
// Movable
|
||||
SlabAllocator(SlabAllocator&& other) noexcept
|
||||
: slabs_(other.slabs_), total_allocated_(other.total_allocated_), total_deallocated_(other.total_deallocated_) {
|
||||
other.slabs_ = nullptr;
|
||||
other.total_allocated_ = 0;
|
||||
other.total_deallocated_ = 0;
|
||||
}
|
||||
|
||||
SlabAllocator& operator=(SlabAllocator&& other) noexcept {
|
||||
if (this != &other) {
|
||||
cleanup();
|
||||
slabs_ = other.slabs_;
|
||||
total_allocated_ = other.total_allocated_;
|
||||
total_deallocated_ = other.total_deallocated_;
|
||||
other.slabs_ = nullptr;
|
||||
other.total_allocated_ = 0;
|
||||
other.total_deallocated_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* allocate(fl::size n = 1) {
|
||||
if (n == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to allocate from slab first
|
||||
void* ptr = allocateFromSlab(n);
|
||||
if (ptr) {
|
||||
fl::memfill(ptr, 0, sizeof(T) * n);
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
// Fall back to regular malloc for large allocations
|
||||
ptr = malloc(sizeof(T) * n);
|
||||
if (ptr) {
|
||||
fl::memfill(ptr, 0, sizeof(T) * n);
|
||||
}
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
void deallocate(T* ptr, fl::size n = 1) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to deallocate from slab first
|
||||
bool found_in_slab = false;
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
u8* slab_start = slab->memory;
|
||||
u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
|
||||
u8* block_ptr = fl::bit_cast_ptr<u8>(static_cast<void*>(ptr));
|
||||
|
||||
if (block_ptr >= slab_start && block_ptr < slab_end) {
|
||||
deallocateToSlab(ptr, n);
|
||||
found_in_slab = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_in_slab) {
|
||||
// This was allocated with regular malloc
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Get allocation statistics
|
||||
fl::size getTotalAllocated() const { return total_allocated_; }
|
||||
fl::size getTotalDeallocated() const { return total_deallocated_; }
|
||||
fl::size getActiveAllocations() const { return total_allocated_ - total_deallocated_; }
|
||||
|
||||
// Get number of slabs
|
||||
fl::size getSlabCount() const {
|
||||
fl::size count = 0;
|
||||
for (Slab* slab = slabs_; slab; slab = slab->next) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Cleanup all slabs
|
||||
void cleanup() {
|
||||
while (slabs_) {
|
||||
Slab* next = slabs_->next;
|
||||
slabs_->~Slab();
|
||||
free(slabs_);
|
||||
slabs_ = next;
|
||||
}
|
||||
total_allocated_ = 0;
|
||||
total_deallocated_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// STL-compatible slab allocator
|
||||
template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
|
||||
class allocator_slab {
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = typename fl::conditional<
|
||||
fl::is_same<U, void>::value,
|
||||
allocator_slab<char, SLAB_SIZE>,
|
||||
allocator_slab<U, SLAB_SIZE>
|
||||
>::type;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_slab() noexcept {}
|
||||
|
||||
// Copy constructor
|
||||
allocator_slab(const allocator_slab& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
allocator_slab& operator=(const allocator_slab& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Template copy constructor
|
||||
template <typename U>
|
||||
allocator_slab(const allocator_slab<U, SLAB_SIZE>& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~allocator_slab() noexcept {}
|
||||
|
||||
private:
|
||||
// Get the shared static allocator instance
|
||||
static SlabAllocator<T, SLAB_SIZE>& get_allocator() {
|
||||
static SlabAllocator<T, SLAB_SIZE> allocator;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
public:
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
// Use a static allocator instance per type/size combination
|
||||
SlabAllocator<T, SLAB_SIZE>& allocator = get_allocator();
|
||||
return allocator.allocate(n);
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
// Use the same static allocator instance
|
||||
SlabAllocator<T, SLAB_SIZE>& allocator = get_allocator();
|
||||
allocator.deallocate(p, n);
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
|
||||
// Cleanup method to clean up the static slab allocator
|
||||
void cleanup() {
|
||||
// Access the same static allocator instance and clean it up
|
||||
static SlabAllocator<T, SLAB_SIZE> allocator;
|
||||
allocator.cleanup();
|
||||
}
|
||||
|
||||
// Equality comparison
|
||||
bool operator==(const allocator_slab& other) const noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return true; // All instances are equivalent
|
||||
}
|
||||
|
||||
bool operator!=(const allocator_slab& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Inlined allocator that stores the first N elements inline
|
||||
// Falls back to the base allocator for additional elements
|
||||
template <typename T, fl::size N, typename BaseAllocator = fl::allocator<T>>
|
||||
class allocator_inlined {
|
||||
private:
|
||||
|
||||
// Inlined storage block
|
||||
struct InlinedStorage {
|
||||
alignas(T) u8 data[N * sizeof(T)];
|
||||
|
||||
InlinedStorage() {
|
||||
fl::memfill(data, 0, sizeof(data));
|
||||
}
|
||||
};
|
||||
|
||||
InlinedStorage m_inlined_storage;
|
||||
BaseAllocator m_base_allocator;
|
||||
fl::size m_inlined_used = 0;
|
||||
fl::bitset_fixed<N> m_free_bits; // Track free slots for inlined memory only
|
||||
fl::size m_active_allocations = 0; // Track current active allocations
|
||||
|
||||
public:
|
||||
// Type definitions required by STL
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
// Rebind allocator to type U
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_inlined<U, N, typename BaseAllocator::template rebind<U>::other>;
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
allocator_inlined() noexcept = default;
|
||||
|
||||
// Copy constructor
|
||||
allocator_inlined(const allocator_inlined& other) noexcept {
|
||||
// Copy inlined data
|
||||
m_inlined_used = other.m_inlined_used;
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
|
||||
}
|
||||
|
||||
// Copy free bits
|
||||
m_free_bits = other.m_free_bits;
|
||||
|
||||
// Note: Heap allocations are not copied, only inlined data
|
||||
|
||||
// Copy active allocations count
|
||||
m_active_allocations = other.m_active_allocations;
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
allocator_inlined& operator=(const allocator_inlined& other) noexcept {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
|
||||
// Copy inlined data
|
||||
m_inlined_used = other.m_inlined_used;
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
|
||||
}
|
||||
|
||||
// Copy free bits
|
||||
m_free_bits = other.m_free_bits;
|
||||
|
||||
// Note: Heap allocations are not copied, only inlined data
|
||||
|
||||
// Copy active allocations count
|
||||
m_active_allocations = other.m_active_allocations;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Template copy constructor
|
||||
template <typename U>
|
||||
allocator_inlined(const allocator_inlined<U, N, typename BaseAllocator::template rebind<U>::other>& other) noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~allocator_inlined() noexcept {
|
||||
clear();
|
||||
}
|
||||
|
||||
// Allocate memory for n objects of type T
|
||||
T* allocate(fl::size n) {
|
||||
if (n == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// For large allocations (n > 1), use base allocator directly
|
||||
if (n > 1) {
|
||||
T* ptr = m_base_allocator.allocate(n);
|
||||
if (ptr) {
|
||||
m_active_allocations += n;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// For single allocations, first try inlined memory
|
||||
// Find first free inlined slot
|
||||
fl::i32 free_slot = m_free_bits.find_first(false);
|
||||
if (free_slot >= 0 && static_cast<fl::size>(free_slot) < N) {
|
||||
// Mark the inlined slot as used
|
||||
m_free_bits.set(static_cast<fl::u32>(free_slot), true);
|
||||
|
||||
// Update inlined usage tracking
|
||||
if (static_cast<fl::size>(free_slot) + 1 > m_inlined_used) {
|
||||
m_inlined_used = static_cast<fl::size>(free_slot) + 1;
|
||||
}
|
||||
m_active_allocations++;
|
||||
return &get_inlined_ptr()[static_cast<fl::size>(free_slot)];
|
||||
}
|
||||
|
||||
// No inlined slots available, use heap allocation
|
||||
T* ptr = m_base_allocator.allocate(1);
|
||||
if (ptr) {
|
||||
m_active_allocations++;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Deallocate memory for n objects of type T
|
||||
void deallocate(T* p, fl::size n) {
|
||||
if (!p || n == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is inlined memory
|
||||
T* inlined_start = get_inlined_ptr();
|
||||
T* inlined_end = inlined_start + N;
|
||||
|
||||
if (p >= inlined_start && p < inlined_end) {
|
||||
// This is inlined memory, mark slots as free
|
||||
fl::size slot_index = (p - inlined_start);
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (slot_index + i < N) {
|
||||
m_free_bits.set(slot_index + i, false); // Mark as free
|
||||
}
|
||||
}
|
||||
m_active_allocations -= n;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fallback to base allocator for heap allocations
|
||||
m_base_allocator.deallocate(p, n);
|
||||
m_active_allocations -= n;
|
||||
}
|
||||
|
||||
// Construct an object at the specified address
|
||||
template <typename U, typename... Args>
|
||||
void construct(U* p, Args&&... args) {
|
||||
if (p == nullptr) return;
|
||||
new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Destroy an object at the specified address
|
||||
template <typename U>
|
||||
void destroy(U* p) {
|
||||
if (p == nullptr) return;
|
||||
p->~U();
|
||||
}
|
||||
|
||||
// Clear all allocated memory
|
||||
void clear() {
|
||||
// Destroy inlined objects
|
||||
for (fl::size i = 0; i < m_inlined_used; ++i) {
|
||||
get_inlined_ptr()[i].~T();
|
||||
}
|
||||
m_inlined_used = 0;
|
||||
m_free_bits.reset();
|
||||
m_active_allocations = 0;
|
||||
|
||||
// Clean up the base allocator (for SlabAllocator, this clears slabs and free lists)
|
||||
cleanup_base_allocator();
|
||||
}
|
||||
|
||||
// Get total allocated size
|
||||
fl::size total_size() const {
|
||||
return m_active_allocations;
|
||||
}
|
||||
|
||||
// Get inlined capacity
|
||||
fl::size inlined_capacity() const {
|
||||
return N;
|
||||
}
|
||||
|
||||
// Check if using inlined storage
|
||||
bool is_using_inlined() const {
|
||||
return m_active_allocations == m_inlined_used;
|
||||
}
|
||||
|
||||
private:
|
||||
T* get_inlined_ptr() {
|
||||
return reinterpret_cast<T*>(m_inlined_storage.data);
|
||||
}
|
||||
|
||||
const T* get_inlined_ptr() const {
|
||||
return reinterpret_cast<const T*>(m_inlined_storage.data);
|
||||
}
|
||||
|
||||
// SFINAE helper to detect if base allocator has cleanup() method
|
||||
template<typename U>
|
||||
static auto has_cleanup_impl(int) -> decltype(fl::declval<U>().cleanup(), fl::true_type{});
|
||||
|
||||
template<typename U>
|
||||
static fl::false_type has_cleanup_impl(...);
|
||||
|
||||
using has_cleanup = decltype(has_cleanup_impl<BaseAllocator>(0));
|
||||
|
||||
// Call cleanup on base allocator if it has the method
|
||||
void cleanup_base_allocator() {
|
||||
cleanup_base_allocator_impl(has_cleanup{});
|
||||
}
|
||||
|
||||
void cleanup_base_allocator_impl(fl::true_type) {
|
||||
m_base_allocator.cleanup();
|
||||
}
|
||||
|
||||
void cleanup_base_allocator_impl(fl::false_type) {
|
||||
// Base allocator doesn't have cleanup method, do nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Equality comparison
|
||||
bool operator==(const allocator_inlined& other) const noexcept {
|
||||
FASTLED_UNUSED(other);
|
||||
return true; // All instances are equivalent for now
|
||||
}
|
||||
|
||||
bool operator!=(const allocator_inlined& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Inlined allocator that uses PSRam for heap allocation
|
||||
template <typename T, fl::size N>
|
||||
using allocator_inlined_psram = allocator_inlined<T, N, fl::allocator_psram<T>>;
|
||||
|
||||
// Inlined allocator that uses slab allocator for heap allocation
|
||||
template <typename T, fl::size N, fl::size SLAB_SIZE = 8>
|
||||
using allocator_inlined_slab_psram = allocator_inlined<T, N, fl::allocator_slab<T, SLAB_SIZE>>;
|
||||
|
||||
|
||||
template <typename T, fl::size N>
|
||||
using allocator_inlined_slab = allocator_inlined<T, N, fl::allocator_slab<T>>;
|
||||
|
||||
} // namespace fl
|
||||
201
libraries/FastLED/src/fl/array.h
Normal file
201
libraries/FastLED/src/fl/array.h
Normal file
@@ -0,0 +1,201 @@
|
||||
// allow-include-after-namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/bit_cast.h"
|
||||
|
||||
#include "fl/initializer_list.h"
|
||||
#include "fl/has_include.h"
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
/**
|
||||
* @brief A fixed-size array implementation similar to std::array
|
||||
*
|
||||
* This class provides a thin wrapper around a C-style array with
|
||||
* STL container-like interface.
|
||||
*
|
||||
* @tparam T The type of elements
|
||||
* @tparam N The number of elements
|
||||
*/
|
||||
template <typename T, fl::size N> class array {
|
||||
public:
|
||||
// Standard container type definitions
|
||||
using value_type = T;
|
||||
using size_type = fl::size;
|
||||
using difference_type = ptrdiff_t;
|
||||
using reference = value_type &;
|
||||
using const_reference = const value_type &;
|
||||
using pointer = value_type *;
|
||||
using const_pointer = const value_type *;
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
|
||||
// Default constructor - elements are default-initialized
|
||||
array() = default;
|
||||
|
||||
// Fill constructor
|
||||
explicit array(const T &value) {
|
||||
// std::fill_n(begin(), N, value);
|
||||
fill_n(data_, N, value);
|
||||
}
|
||||
|
||||
// Initializer list constructor
|
||||
array(fl::initializer_list<T> list) {
|
||||
fl::size i = 0;
|
||||
for (auto it = list.begin(); it != list.end() && i < N; ++it, ++i) {
|
||||
data_[i] = *it;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
array(const array &) = default;
|
||||
|
||||
// Move constructor
|
||||
array(array &&) = default;
|
||||
|
||||
// Copy assignment
|
||||
array &operator=(const array &) = default;
|
||||
|
||||
// Move assignment
|
||||
array &operator=(array &&) = default;
|
||||
|
||||
// Element access
|
||||
T &at(fl::size pos) {
|
||||
if (pos >= N) {
|
||||
return error_value();
|
||||
}
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
const T &at(fl::size pos) const {
|
||||
if (pos >= N) {
|
||||
return error_value();
|
||||
}
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
T &operator[](fl::size pos) { return data_[pos]; }
|
||||
|
||||
const_reference operator[](fl::size pos) const { return data_[pos]; }
|
||||
|
||||
T &front() { return data_[0]; }
|
||||
|
||||
const T &front() const { return data_[0]; }
|
||||
|
||||
T &back() { return data_[N - 1]; }
|
||||
|
||||
const T &back() const { return data_[N - 1]; }
|
||||
|
||||
pointer data() noexcept { return data_; }
|
||||
|
||||
const_pointer data() const noexcept { return data_; }
|
||||
|
||||
// Iterators
|
||||
iterator begin() noexcept { return data_; }
|
||||
|
||||
const_iterator begin() const noexcept { return data_; }
|
||||
|
||||
const_iterator cbegin() const noexcept { return data_; }
|
||||
|
||||
iterator end() noexcept { return data_ + N; }
|
||||
|
||||
const_iterator end() const noexcept { return data_ + N; }
|
||||
|
||||
// Capacity
|
||||
bool empty() const noexcept { return N == 0; }
|
||||
|
||||
fl::size size() const noexcept { return N; }
|
||||
|
||||
fl::size max_size() const noexcept { return N; }
|
||||
|
||||
// Operations
|
||||
void fill(const T &value) {
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
data_[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(array &other) {
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
fl::swap(data_[i], other.data_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static T &error_value() {
|
||||
static T empty_value;
|
||||
return empty_value;
|
||||
}
|
||||
T data_[N];
|
||||
};
|
||||
|
||||
// Non-member functions
|
||||
template <typename T, fl::size N>
|
||||
bool operator==(const array<T, N> &lhs, const array<T, N> &rhs) {
|
||||
// return std::equal(lhs.begin(), lhs.end(), rhs.begin());
|
||||
for (fl::size i = 0; i < N; ++i) {
|
||||
if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, fl::size N>
|
||||
bool operator!=(const array<T, N> &lhs, const array<T, N> &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename T, fl::size N>
|
||||
void swap(array<T, N> &lhs,
|
||||
array<T, N> &rhs) noexcept(noexcept(lhs.swap(rhs))) {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
// FASTLED_STACK_ARRAY
|
||||
// An array of variable length that is allocated on the stack using
|
||||
// either alloca or a variable length array (VLA) support built into the
|
||||
// the compiler.
|
||||
// Example:
|
||||
// Instead of: int array[buff_size];
|
||||
// You'd use: FASTLED_STACK_ARRAY(int, array, buff_size);
|
||||
|
||||
#ifndef FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
#if defined(__clang__) || defined(ARDUINO_GIGA_M7) || defined(ARDUINO_GIGA)
|
||||
// Clang doesn't have variable length arrays. Therefore we need to emulate them
|
||||
// using alloca. It's been found that Arduino Giga M7 also doesn't support
|
||||
// variable length arrays for some reason so we force it to emulate them as well
|
||||
// in this case.
|
||||
#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 1
|
||||
#else
|
||||
// Else, assume the compiler is gcc, which has variable length arrays
|
||||
#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 0
|
||||
#endif
|
||||
#endif // FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
|
||||
#if !FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE NAME[SIZE]; \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif FL_HAS_INCLUDE(<alloca.h>)
|
||||
#include <alloca.h>
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = fl::bit_cast_ptr<TYPE>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif FL_HAS_INCLUDE(<cstdlib>)
|
||||
#include <cstdlib> // ok include
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = fl::bit_cast_ptr<TYPE>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#else
|
||||
#error "Compiler does not allow variable type arrays."
|
||||
#endif
|
||||
8
libraries/FastLED/src/fl/assert.h
Normal file
8
libraries/FastLED/src/fl/assert.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "platforms/assert_defs.h"
|
||||
|
||||
#ifndef FL_ASSERT
|
||||
#define FL_ASSERT(x, MSG) FASTLED_ASSERT(x, MSG)
|
||||
#define FL_ASSERT_IF FASTLED_ASSERT_IF
|
||||
#endif
|
||||
211
libraries/FastLED/src/fl/async.cpp
Normal file
211
libraries/FastLED/src/fl/async.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "fl/async.h"
|
||||
#include "fl/functional.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/task.h"
|
||||
#include "fl/time.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
// Platform-specific includes
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" void emscripten_sleep(unsigned int ms);
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
AsyncManager& AsyncManager::instance() {
|
||||
return fl::Singleton<AsyncManager>::instance();
|
||||
}
|
||||
|
||||
void AsyncManager::register_runner(async_runner* runner) {
|
||||
if (runner && fl::find(mRunners.begin(), mRunners.end(), runner) == mRunners.end()) {
|
||||
mRunners.push_back(runner);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncManager::unregister_runner(async_runner* runner) {
|
||||
auto it = fl::find(mRunners.begin(), mRunners.end(), runner);
|
||||
if (it != mRunners.end()) {
|
||||
mRunners.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncManager::update_all() {
|
||||
// Update all registered runners
|
||||
for (auto* runner : mRunners) {
|
||||
if (runner) {
|
||||
runner->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncManager::has_active_tasks() const {
|
||||
for (const auto* runner : mRunners) {
|
||||
if (runner && runner->has_active_tasks()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t AsyncManager::total_active_tasks() const {
|
||||
size_t total = 0;
|
||||
for (const auto* runner : mRunners) {
|
||||
if (runner) {
|
||||
total += runner->active_task_count();
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Public API functions
|
||||
|
||||
void async_run() {
|
||||
fl::Scheduler::instance().update();
|
||||
AsyncManager::instance().update_all();
|
||||
}
|
||||
|
||||
void async_yield() {
|
||||
// Always pump all async tasks first
|
||||
async_run();
|
||||
|
||||
// Platform-specific yielding behavior
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// WASM: Use emscripten_sleep to yield control to browser event loop
|
||||
emscripten_sleep(1); // Sleep for 1ms to yield to browser
|
||||
#endif
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
async_run(); // Give other async tasks a chance
|
||||
}
|
||||
}
|
||||
|
||||
size_t async_active_tasks() {
|
||||
return AsyncManager::instance().total_active_tasks();
|
||||
}
|
||||
|
||||
bool async_has_tasks() {
|
||||
return AsyncManager::instance().has_active_tasks();
|
||||
}
|
||||
|
||||
// Scheduler implementation
|
||||
Scheduler& Scheduler::instance() {
|
||||
return fl::Singleton<Scheduler>::instance();
|
||||
}
|
||||
|
||||
int Scheduler::add_task(task t) {
|
||||
if (t.get_impl()) {
|
||||
t.get_impl()->mTaskId = mNextTaskId++;
|
||||
int task_id = t.get_impl()->mTaskId;
|
||||
mTasks.push_back(fl::move(t));
|
||||
return task_id;
|
||||
}
|
||||
return 0; // Invalid task
|
||||
}
|
||||
|
||||
void Scheduler::update() {
|
||||
uint32_t current_time = fl::time();
|
||||
|
||||
// Use index-based iteration to avoid iterator invalidation issues
|
||||
for (fl::size i = 0; i < mTasks.size();) {
|
||||
task& t = mTasks[i];
|
||||
auto impl = t.get_impl();
|
||||
|
||||
if (!impl || impl->is_canceled()) {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else {
|
||||
// Check if task is ready to run (frame tasks will return false here)
|
||||
bool should_run = impl->ready_to_run(current_time);
|
||||
|
||||
if (should_run) {
|
||||
// Update last run time for recurring tasks
|
||||
impl->set_last_run_time(current_time);
|
||||
|
||||
// Execute the task
|
||||
if (impl->has_then()) {
|
||||
impl->execute_then();
|
||||
} else {
|
||||
warn_no_then(impl->id(), impl->trace_label());
|
||||
}
|
||||
|
||||
// Remove one-shot tasks, keep recurring ones
|
||||
bool is_recurring = (impl->type() == TaskType::kEveryMs || impl->type() == TaskType::kAtFramerate);
|
||||
if (is_recurring) {
|
||||
++i; // Keep recurring tasks
|
||||
} else {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
}
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::update_before_frame_tasks() {
|
||||
update_tasks_of_type(TaskType::kBeforeFrame);
|
||||
}
|
||||
|
||||
void Scheduler::update_after_frame_tasks() {
|
||||
update_tasks_of_type(TaskType::kAfterFrame);
|
||||
}
|
||||
|
||||
void Scheduler::update_tasks_of_type(TaskType task_type) {
|
||||
uint32_t current_time = fl::time();
|
||||
|
||||
// Use index-based iteration to avoid iterator invalidation issues
|
||||
for (fl::size i = 0; i < mTasks.size();) {
|
||||
task& t = mTasks[i];
|
||||
auto impl = t.get_impl();
|
||||
|
||||
if (!impl || impl->is_canceled()) {
|
||||
// erase() returns bool in HeapVector, not iterator
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else if (impl->type() == task_type) {
|
||||
// This is a frame task of the type we're looking for
|
||||
bool should_run = impl->ready_to_run_frame_task(current_time);
|
||||
|
||||
if (should_run) {
|
||||
// Update last run time for frame tasks (though they don't use it)
|
||||
impl->set_last_run_time(current_time);
|
||||
|
||||
// Execute the task
|
||||
if (impl->has_then()) {
|
||||
impl->execute_then();
|
||||
} else {
|
||||
warn_no_then(impl->id(), impl->trace_label());
|
||||
}
|
||||
|
||||
// Frame tasks are always one-shot, so remove them after execution
|
||||
mTasks.erase(mTasks.begin() + i);
|
||||
// Don't increment i since we just removed an element
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
++i; // Not the task type we're looking for
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::warn_no_then(int task_id, const fl::string& trace_label) {
|
||||
if (!trace_label.empty()) {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id << " launched at " << trace_label);
|
||||
} else {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::warn_no_catch(int task_id, const fl::string& trace_label, const Error& error) {
|
||||
if (!trace_label.empty()) {
|
||||
FL_WARN(fl::string("[fl::task] Warning: no catch_() callback set for Task#") << task_id << " launched at " << trace_label << ". Error: " << error.message);
|
||||
} else {
|
||||
FL_WARN(fl::string("[fl/task] Warning: no catch_() callback set for Task#") << task_id << ". Error: " << error.message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
260
libraries/FastLED/src/fl/async.h
Normal file
260
libraries/FastLED/src/fl/async.h
Normal file
@@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
/// @file async.h
|
||||
/// @brief Generic asynchronous task management for FastLED
|
||||
///
|
||||
/// This module provides a unified system for managing asynchronous operations
|
||||
/// across FastLED, including HTTP requests, timers, and other background tasks.
|
||||
///
|
||||
/// The async system integrates with FastLED's engine events and can be pumped
|
||||
/// during delay() calls on WASM platforms for optimal responsiveness.
|
||||
///
|
||||
/// @section Usage
|
||||
/// @code
|
||||
/// #include "fl/async.h"
|
||||
///
|
||||
/// // Create a custom async runner
|
||||
/// class Myasync_runner : public fl::async_runner {
|
||||
/// public:
|
||||
/// void update() override {
|
||||
/// // Process your async tasks here
|
||||
/// process_timers();
|
||||
/// handle_network_events();
|
||||
/// }
|
||||
///
|
||||
/// bool has_active_tasks() const override {
|
||||
/// return !mTimers.empty() || mNetworkActive;
|
||||
/// }
|
||||
///
|
||||
/// size_t active_task_count() const override {
|
||||
/// return mTimers.size() + (mNetworkActive ? 1 : 0);
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// void setup() {
|
||||
/// Myasync_runner* runner = new Myasync_runner();
|
||||
/// fl::AsyncManager::instance().register_runner(runner);
|
||||
///
|
||||
/// // Now your async tasks will be automatically updated during:
|
||||
/// // - FastLED.show() calls (via engine events)
|
||||
/// // - delay() calls on WASM platforms
|
||||
/// // - Manual fl::async_run() calls
|
||||
/// }
|
||||
/// @endcode
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/promise.h"
|
||||
#include "fl/promise_result.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/thread_local.h"
|
||||
|
||||
#include "fl/task.h"
|
||||
#include "fl/time.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class AsyncManager;
|
||||
|
||||
/// @brief Generic asynchronous task runner interface
|
||||
class async_runner {
|
||||
public:
|
||||
virtual ~async_runner() = default;
|
||||
|
||||
/// Update this async runner (called during async pumping)
|
||||
virtual void update() = 0;
|
||||
|
||||
/// Check if this runner has active tasks
|
||||
virtual bool has_active_tasks() const = 0;
|
||||
|
||||
/// Get number of active tasks (for debugging/monitoring)
|
||||
virtual size_t active_task_count() const = 0;
|
||||
};
|
||||
|
||||
/// @brief Async task manager (singleton)
|
||||
class AsyncManager {
|
||||
public:
|
||||
static AsyncManager& instance();
|
||||
|
||||
/// Register an async runner
|
||||
void register_runner(async_runner* runner);
|
||||
|
||||
/// Unregister an async runner
|
||||
void unregister_runner(async_runner* runner);
|
||||
|
||||
/// Update all registered async runners
|
||||
void update_all();
|
||||
|
||||
/// Check if there are any active async tasks
|
||||
bool has_active_tasks() const;
|
||||
|
||||
/// Get total number of active tasks across all runners
|
||||
size_t total_active_tasks() const;
|
||||
|
||||
private:
|
||||
fl::vector<async_runner*> mRunners;
|
||||
};
|
||||
|
||||
/// @brief Platform-specific async yield function
|
||||
///
|
||||
/// This function pumps all async tasks and yields control appropriately for the platform:
|
||||
/// - WASM: calls async_run() then emscripten_sleep(1) to yield to browser
|
||||
/// - Other platforms: calls async_run() multiple times with simple yielding
|
||||
///
|
||||
/// This centralizes platform-specific async behavior instead of having #ifdef in generic code.
|
||||
void async_yield();
|
||||
|
||||
|
||||
/// @brief Run all registered async tasks once
|
||||
///
|
||||
/// This function updates all registered async runners (fetch, timers, etc.)
|
||||
/// and is automatically called during:
|
||||
/// - FastLED engine events (onEndFrame)
|
||||
/// - delay() calls on WASM platforms (every 1ms)
|
||||
/// - Manual calls for custom async pumping
|
||||
///
|
||||
/// @note This replaces the old fetch_update() function with a generic approach
|
||||
void async_run();
|
||||
|
||||
|
||||
|
||||
/// @brief Get the number of active async tasks across all systems
|
||||
/// @return Total number of active async tasks
|
||||
size_t async_active_tasks();
|
||||
|
||||
/// @brief Check if any async systems have active tasks
|
||||
/// @return True if any async tasks are running
|
||||
bool async_has_tasks();
|
||||
|
||||
/// @brief Synchronously wait for a promise to complete (ONLY safe in top-level contexts)
|
||||
/// @tparam T The type of value the promise resolves to (automatically deduced)
|
||||
/// @param promise The promise to wait for
|
||||
/// @return A PromiseResult containing either the resolved value T or an Error
|
||||
///
|
||||
/// This function blocks until the promise is either resolved or rejected,
|
||||
/// then returns a PromiseResult that can be checked with ok() for success/failure.
|
||||
/// While waiting, it continuously calls async_yield() to pump async tasks and yield appropriately.
|
||||
///
|
||||
/// **SAFETY WARNING**: This function should ONLY be called from top-level contexts
|
||||
/// like Arduino loop() function. Never call this from:
|
||||
/// - Promise callbacks (.then, .catch_)
|
||||
/// - Nested async operations
|
||||
/// - Interrupt handlers
|
||||
/// - Library initialization code
|
||||
///
|
||||
/// The "_top_level" suffix emphasizes this safety requirement.
|
||||
///
|
||||
/// **Type Deduction**: The template parameter T is automatically deduced from the
|
||||
/// promise parameter, so you don't need to specify it explicitly.
|
||||
///
|
||||
/// @section Usage
|
||||
/// @code
|
||||
/// auto promise = fl::fetch_get("http://example.com");
|
||||
/// auto result = fl::await_top_level(promise); // Type automatically deduced!
|
||||
///
|
||||
/// if (result.ok()) {
|
||||
/// const Response& resp = result.value();
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// } else {
|
||||
/// FL_WARN("Error: " << result.error().message);
|
||||
/// }
|
||||
///
|
||||
/// // Or use operator bool
|
||||
/// if (result) {
|
||||
/// FL_WARN("Got response: " << result.value().status());
|
||||
/// }
|
||||
///
|
||||
/// // You can still specify the type explicitly if needed:
|
||||
/// auto explicit_result = fl::await_top_level<response>(promise);
|
||||
/// @endcode
|
||||
template<typename T>
|
||||
fl::result<T> await_top_level(fl::promise<T> promise) {
|
||||
// Handle invalid promises
|
||||
if (!promise.valid()) {
|
||||
return fl::result<T>(Error("Invalid promise"));
|
||||
}
|
||||
|
||||
// If already completed, return immediately
|
||||
if (promise.is_completed()) {
|
||||
if (promise.is_resolved()) {
|
||||
return fl::result<T>(promise.value());
|
||||
} else {
|
||||
return fl::result<T>(promise.error());
|
||||
}
|
||||
}
|
||||
|
||||
// Track recursion depth to prevent infinite loops
|
||||
static fl::ThreadLocal<int> await_depth(0);
|
||||
if (await_depth.access() > 10) {
|
||||
return fl::result<T>(Error("await_top_level recursion limit exceeded - possible infinite loop"));
|
||||
}
|
||||
|
||||
++await_depth.access();
|
||||
|
||||
// Wait for promise to complete while pumping async tasks
|
||||
int pump_count = 0;
|
||||
const int max_pump_iterations = 10000; // Safety limit
|
||||
|
||||
while (!promise.is_completed() && pump_count < max_pump_iterations) {
|
||||
// Update the promise first (in case it's not managed by async system)
|
||||
promise.update();
|
||||
|
||||
// Check if completed after update
|
||||
if (promise.is_completed()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Platform-agnostic async pump and yield
|
||||
async_yield();
|
||||
|
||||
++pump_count;
|
||||
}
|
||||
|
||||
--await_depth.access();
|
||||
|
||||
// Check for timeout
|
||||
if (pump_count >= max_pump_iterations) {
|
||||
return fl::result<T>(Error("await_top_level timeout - promise did not complete"));
|
||||
}
|
||||
|
||||
// Return the result
|
||||
if (promise.is_resolved()) {
|
||||
return fl::result<T>(promise.value());
|
||||
} else {
|
||||
return fl::result<T>(promise.error());
|
||||
}
|
||||
}
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
static Scheduler& instance();
|
||||
|
||||
int add_task(task t);
|
||||
void update();
|
||||
|
||||
// New methods for frame task handling
|
||||
void update_before_frame_tasks();
|
||||
void update_after_frame_tasks();
|
||||
|
||||
// For testing: clear all tasks
|
||||
void clear_all_tasks() { mTasks.clear(); mNextTaskId = 1; }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<Scheduler>;
|
||||
Scheduler() : mTasks() {}
|
||||
|
||||
void warn_no_then(int task_id, const fl::string& trace_label);
|
||||
void warn_no_catch(int task_id, const fl::string& trace_label, const Error& error);
|
||||
|
||||
// Helper method for running specific task types
|
||||
void update_tasks_of_type(TaskType task_type);
|
||||
|
||||
fl::vector<task> mTasks;
|
||||
int mNextTaskId = 1;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
160
libraries/FastLED/src/fl/atomic.h
Normal file
160
libraries/FastLED/src/fl/atomic.h
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/thread.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/align.h"
|
||||
|
||||
#if FASTLED_MULTITHREADED
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
|
||||
#if FASTLED_MULTITHREADED
|
||||
template <typename T>
|
||||
using atomic = std::atomic<T>;
|
||||
#else
|
||||
template <typename T> class AtomicFake;
|
||||
template <typename T>
|
||||
using atomic = AtomicFake<T>;
|
||||
#endif
|
||||
|
||||
using atomic_bool = atomic<bool>;
|
||||
using atomic_int = atomic<int>;
|
||||
using atomic_uint = atomic<unsigned int>;
|
||||
using atomic_u32 = atomic<fl::u32>;
|
||||
using atomic_i32 = atomic<fl::i32>;
|
||||
|
||||
///////////////////// IMPLEMENTATION //////////////////////////////////////
|
||||
|
||||
template <typename T> class AtomicFake {
|
||||
public:
|
||||
AtomicFake() : mValue{} {}
|
||||
explicit AtomicFake(T value) : mValue(value) {}
|
||||
|
||||
// Non-copyable and non-movable
|
||||
AtomicFake(const AtomicFake&) = delete;
|
||||
AtomicFake& operator=(const AtomicFake&) = delete;
|
||||
AtomicFake(AtomicFake&&) = delete;
|
||||
AtomicFake& operator=(AtomicFake&&) = delete;
|
||||
|
||||
// Basic atomic operations - fake implementation (not actually atomic)
|
||||
T load() const {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
void store(T value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
T exchange(T value) {
|
||||
T old = mValue;
|
||||
mValue = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
bool compare_exchange_weak(T& expected, T desired) {
|
||||
if (mValue == expected) {
|
||||
mValue = desired;
|
||||
return true;
|
||||
} else {
|
||||
expected = mValue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool compare_exchange_strong(T& expected, T desired) {
|
||||
return compare_exchange_weak(expected, desired);
|
||||
}
|
||||
|
||||
// Assignment operator
|
||||
T operator=(T value) {
|
||||
store(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Conversion operator
|
||||
operator T() const {
|
||||
return load();
|
||||
}
|
||||
|
||||
// Arithmetic operators (for integral and floating point types)
|
||||
T operator++() {
|
||||
return ++mValue;
|
||||
}
|
||||
|
||||
T operator++(int) {
|
||||
return mValue++;
|
||||
}
|
||||
|
||||
T operator--() {
|
||||
return --mValue;
|
||||
}
|
||||
|
||||
T operator--(int) {
|
||||
return mValue--;
|
||||
}
|
||||
|
||||
T operator+=(T value) {
|
||||
mValue += value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator-=(T value) {
|
||||
mValue -= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator&=(T value) {
|
||||
mValue &= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator|=(T value) {
|
||||
mValue |= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
T operator^=(T value) {
|
||||
mValue ^= value;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
// Fetch operations
|
||||
T fetch_add(T value) {
|
||||
T old = mValue;
|
||||
mValue += value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_sub(T value) {
|
||||
T old = mValue;
|
||||
mValue -= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_and(T value) {
|
||||
T old = mValue;
|
||||
mValue &= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_or(T value) {
|
||||
T old = mValue;
|
||||
mValue |= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
T fetch_xor(T value) {
|
||||
T old = mValue;
|
||||
mValue ^= value;
|
||||
return old;
|
||||
}
|
||||
|
||||
private:
|
||||
FL_ALIGN_AS(T) T mValue;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
194
libraries/FastLED/src/fl/audio.cpp
Normal file
194
libraries/FastLED/src/fl/audio.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
|
||||
#include "audio.h"
|
||||
#include "fl/thread_local.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/mutex.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
FFT &get_flex_fft() {
|
||||
static ThreadLocal<FFT> gFlexFFT;
|
||||
return gFlexFFT.access();
|
||||
}
|
||||
|
||||
// Object pool implementation
|
||||
|
||||
struct AudioSamplePool {
|
||||
static AudioSamplePool& instance() {
|
||||
static AudioSamplePool s_pool;
|
||||
return s_pool;
|
||||
}
|
||||
void put(AudioSampleImplPtr&& impl) {
|
||||
if (impl.unique()) {
|
||||
// There is no more shared_ptr to this object, so we can recycle it.
|
||||
fl::lock_guard<fl::mutex> lock(mutex);
|
||||
if (impl && pool.size() < MAX_POOL_SIZE) {
|
||||
// Reset the impl for reuse (clear internal state)
|
||||
impl->reset();
|
||||
pool.push_back(impl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Pool is full, discard the impl
|
||||
impl.reset();
|
||||
}
|
||||
AudioSampleImplPtr getOrCreate() {
|
||||
{
|
||||
fl::lock_guard<fl::mutex> lock(mutex);
|
||||
if (!pool.empty()) {
|
||||
AudioSampleImplPtr impl = pool.back();
|
||||
pool.pop_back();
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
return fl::make_shared<AudioSampleImpl>();
|
||||
}
|
||||
|
||||
fl::vector<AudioSampleImplPtr> pool;
|
||||
static constexpr fl::size MAX_POOL_SIZE = 8;
|
||||
fl::mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
AudioSample::~AudioSample() {
|
||||
if (mImpl) {
|
||||
AudioSamplePool::instance().put(fl::move(mImpl));
|
||||
}
|
||||
}
|
||||
|
||||
const AudioSample::VectorPCM &AudioSample::pcm() const {
|
||||
if (isValid()) {
|
||||
return mImpl->pcm();
|
||||
}
|
||||
static VectorPCM empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
AudioSample &AudioSample::operator=(const AudioSample &other) {
|
||||
mImpl = other.mImpl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
fl::size AudioSample::size() const {
|
||||
if (isValid()) {
|
||||
return mImpl->pcm().size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const fl::i16 &AudioSample::at(fl::size i) const {
|
||||
if (i < size()) {
|
||||
return pcm()[i];
|
||||
}
|
||||
return empty()[0];
|
||||
}
|
||||
|
||||
const fl::i16 &AudioSample::operator[](fl::size i) const { return at(i); }
|
||||
|
||||
bool AudioSample::operator==(const AudioSample &other) const {
|
||||
if (mImpl == other.mImpl) {
|
||||
return true;
|
||||
}
|
||||
if (mImpl == nullptr || other.mImpl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (mImpl->pcm().size() != other.mImpl->pcm().size()) {
|
||||
return false;
|
||||
}
|
||||
for (fl::size i = 0; i < mImpl->pcm().size(); ++i) {
|
||||
if (mImpl->pcm()[i] != other.mImpl->pcm()[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioSample::operator!=(const AudioSample &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
const AudioSample::VectorPCM &AudioSample::empty() {
|
||||
static fl::i16 empty_data[1] = {0};
|
||||
static VectorPCM empty(empty_data);
|
||||
return empty;
|
||||
}
|
||||
|
||||
float AudioSample::zcf() const { return mImpl->zcf(); }
|
||||
|
||||
fl::u32 AudioSample::timestamp() const {
|
||||
if (isValid()) {
|
||||
return mImpl->timestamp();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float AudioSample::rms() const {
|
||||
if (!isValid()) {
|
||||
return 0.0f;
|
||||
}
|
||||
fl::u64 sum_sq = 0;
|
||||
const int N = size();
|
||||
for (int i = 0; i < N; ++i) {
|
||||
fl::i32 x32 = fl::i32(pcm()[i]);
|
||||
sum_sq += x32 * x32;
|
||||
}
|
||||
float rms = sqrtf(float(sum_sq) / N);
|
||||
return rms;
|
||||
}
|
||||
|
||||
SoundLevelMeter::SoundLevelMeter(double spl_floor, double smoothing_alpha)
|
||||
: spl_floor_(spl_floor), smoothing_alpha_(smoothing_alpha),
|
||||
dbfs_floor_global_(INFINITY_DOUBLE), offset_(0.0), current_dbfs_(0.0),
|
||||
current_spl_(spl_floor) {}
|
||||
|
||||
void SoundLevelMeter::processBlock(const fl::i16 *samples, fl::size count) {
|
||||
// 1) compute block power → dBFS
|
||||
double sum_sq = 0.0;
|
||||
for (fl::size i = 0; i < count; ++i) {
|
||||
double s = samples[i] / 32768.0; // normalize to ±1
|
||||
sum_sq += s * s;
|
||||
}
|
||||
double p = sum_sq / count; // mean power
|
||||
double dbfs = 10.0 * log10(p + 1e-12);
|
||||
current_dbfs_ = dbfs;
|
||||
|
||||
// 2) update global floor (with optional smoothing)
|
||||
if (dbfs < dbfs_floor_global_) {
|
||||
if (smoothing_alpha_ <= 0.0) {
|
||||
dbfs_floor_global_ = dbfs;
|
||||
} else {
|
||||
dbfs_floor_global_ = smoothing_alpha_ * dbfs +
|
||||
(1.0 - smoothing_alpha_) * dbfs_floor_global_;
|
||||
}
|
||||
offset_ = spl_floor_ - dbfs_floor_global_;
|
||||
}
|
||||
|
||||
// 3) estimate SPL
|
||||
current_spl_ = dbfs + offset_;
|
||||
}
|
||||
|
||||
void AudioSample::fft(FFTBins *out) const {
|
||||
fl::span<const fl::i16> sample = pcm();
|
||||
FFT_Args args;
|
||||
args.samples = sample.size();
|
||||
args.bands = out->size();
|
||||
args.fmin = FFT_Args::DefaultMinFrequency();
|
||||
args.fmax = FFT_Args::DefaultMaxFrequency();
|
||||
args.sample_rate =
|
||||
FFT_Args::DefaultSampleRate(); // TODO: get sample rate from AudioSample
|
||||
get_flex_fft().run(sample, out, args);
|
||||
}
|
||||
|
||||
|
||||
AudioSample::AudioSample(fl::span<const fl::i16> span, fl::u32 timestamp) {
|
||||
mImpl = AudioSamplePool::instance().getOrCreate();
|
||||
auto begin = span.data();
|
||||
auto end = begin + span.size();
|
||||
mImpl->assign(begin, end, timestamp);
|
||||
}
|
||||
|
||||
|
||||
} // namespace fl
|
||||
169
libraries/FastLED/src/fl/audio.h
Normal file
169
libraries/FastLED/src/fl/audio.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/int.h"
|
||||
#include <math.h>
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/span.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class AudioSampleImpl;
|
||||
|
||||
FASTLED_SMART_PTR(AudioSampleImpl);
|
||||
|
||||
// AudioSample is a wrapper around AudioSampleImpl, hiding the reference
|
||||
// counting so that the api object can be simple and have standard object
|
||||
// semantics.
|
||||
class AudioSample {
|
||||
public:
|
||||
using VectorPCM = fl::vector<fl::i16>;
|
||||
using const_iterator = VectorPCM::const_iterator;
|
||||
AudioSample() {}
|
||||
AudioSample(const AudioSample &other) : mImpl(other.mImpl) {}
|
||||
AudioSample(AudioSampleImplPtr impl) : mImpl(impl) {}
|
||||
~AudioSample();
|
||||
|
||||
// Constructor that takes raw audio data and handles pooling internally
|
||||
AudioSample(fl::span<const fl::i16> span, fl::u32 timestamp = 0);
|
||||
|
||||
|
||||
AudioSample &operator=(const AudioSample &other);
|
||||
bool isValid() const { return mImpl != nullptr; }
|
||||
|
||||
fl::size size() const;
|
||||
// Raw pcm levels.
|
||||
const VectorPCM &pcm() const;
|
||||
// Zero crossing factor between 0.0f -> 1.0f, detects "hiss"
|
||||
// and sounds like cloths rubbing. Useful for sound analysis.
|
||||
float zcf() const;
|
||||
float rms() const;
|
||||
fl::u32 timestamp() const; // Timestamp when sample became valid (millis)
|
||||
|
||||
void fft(FFTBins *out) const;
|
||||
|
||||
const_iterator begin() const { return pcm().begin(); }
|
||||
const_iterator end() const { return pcm().end(); }
|
||||
const fl::i16 &at(fl::size i) const;
|
||||
const fl::i16 &operator[](fl::size i) const;
|
||||
operator bool() const { return isValid(); }
|
||||
bool operator==(const AudioSample &other) const;
|
||||
bool operator!=(const AudioSample &other) const;
|
||||
|
||||
private:
|
||||
static const VectorPCM &empty();
|
||||
AudioSampleImplPtr mImpl;
|
||||
};
|
||||
|
||||
// Sound level meter is a persistant measuring class that will auto-tune the
|
||||
// microphone to real world SPL levels. It will adapt to the noise floor of the
|
||||
// environment. Note that the microphone only ever outputs DBFS (dB Full Scale)
|
||||
// values, which are collected over a stream of samples. The sound level meter
|
||||
// will convert this to SPL (Sound Pressure Level) values, which are the real
|
||||
// world values.
|
||||
class SoundLevelMeter {
|
||||
public:
|
||||
/// @param spl_floor The SPL (dB SPL) that corresponds to your true
|
||||
/// noise-floor.
|
||||
/// @param smoothing_alpha [0…1] how quickly to adapt floor; 0=instant min.
|
||||
SoundLevelMeter(double spl_floor = 33.0, double smoothing_alpha = 0.0);
|
||||
|
||||
/// Process a block of int16 PCM samples.
|
||||
void processBlock(const fl::i16 *samples, fl::size count);
|
||||
void processBlock(fl::span<const fl::i16> samples) {
|
||||
processBlock(samples.data(), samples.size());
|
||||
}
|
||||
|
||||
/// @returns most recent block’s level in dBFS (≤ 0)
|
||||
double getDBFS() const { return current_dbfs_; }
|
||||
|
||||
/// @returns calibrated estimate in dB SPL
|
||||
double getSPL() const { return current_spl_; }
|
||||
|
||||
/// change your known noise-floor SPL at runtime
|
||||
void setFloorSPL(double spl_floor) {
|
||||
spl_floor_ = spl_floor;
|
||||
offset_ = spl_floor_ - dbfs_floor_global_;
|
||||
}
|
||||
|
||||
/// reset so the next quiet block will re-initialize your floor
|
||||
void resetFloor() {
|
||||
dbfs_floor_global_ = INFINITY_DOUBLE; // infinity<double>
|
||||
offset_ = 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
double spl_floor_; // e.g. 33.0 dB SPL
|
||||
double smoothing_alpha_; // 0 = pure min, >0 = slow adapt
|
||||
double dbfs_floor_global_; // lowest dBFS seen so far
|
||||
double offset_; // spl_floor_ − dbfs_floor_global_
|
||||
double current_dbfs_; // last block’s dBFS
|
||||
double current_spl_; // last block’s estimated SPL
|
||||
};
|
||||
|
||||
// Implementation details.
|
||||
class AudioSampleImpl {
|
||||
public:
|
||||
using VectorPCM = fl::vector<fl::i16>;
|
||||
~AudioSampleImpl() {}
|
||||
// template <typename It> void assign(It begin, It end) {
|
||||
// assign(begin, end, 0); // Default timestamp to 0
|
||||
// }
|
||||
template <typename It> void assign(It begin, It end, fl::u32 timestamp) {
|
||||
mSignedPcm.assign(begin, end);
|
||||
mTimestamp = timestamp;
|
||||
// calculate zero crossings
|
||||
initZeroCrossings();
|
||||
}
|
||||
const VectorPCM &pcm() const { return mSignedPcm; }
|
||||
fl::u32 timestamp() const { return mTimestamp; }
|
||||
|
||||
// For object pool - reset internal state for reuse
|
||||
void reset() {
|
||||
mSignedPcm.clear();
|
||||
mZeroCrossings = 0;
|
||||
mTimestamp = 0;
|
||||
}
|
||||
|
||||
// "Zero crossing factor". High values > .4 indicate hissing
|
||||
// sounds. For example a microphone rubbing against a clothing.
|
||||
// These types of signals indicate the audio should be ignored.
|
||||
// Low zero crossing factors (with loud sound) indicate that there
|
||||
// is organized sound like that coming from music. This is so cheap
|
||||
// to calculate it's done automatically. It should be one of the first
|
||||
// signals to reject or accept a sound signal.
|
||||
//
|
||||
// Returns: a value -> [0.0f, 1.0f)
|
||||
float zcf() const {
|
||||
const fl::size n = pcm().size();
|
||||
if (n < 2) {
|
||||
return 0.f;
|
||||
}
|
||||
return float(mZeroCrossings) / static_cast<float>(n - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
void initZeroCrossings() {
|
||||
mZeroCrossings = 0;
|
||||
if (mSignedPcm.size() > 1) {
|
||||
for (fl::size i = 1; i < mSignedPcm.size(); ++i) {
|
||||
const bool crossed =
|
||||
(mSignedPcm[i - 1] < 0 && mSignedPcm[i] >= 0) ||
|
||||
(mSignedPcm[i - 1] >= 0 && mSignedPcm[i] < 0);
|
||||
if (crossed) {
|
||||
++mZeroCrossings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VectorPCM mSignedPcm;
|
||||
fl::i16 mZeroCrossings = 0;
|
||||
fl::u32 mTimestamp = 0;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
74
libraries/FastLED/src/fl/audio_input.cpp
Normal file
74
libraries/FastLED/src/fl/audio_input.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
|
||||
|
||||
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/has_include.h"
|
||||
#include "platforms/audio_input_null.hpp"
|
||||
|
||||
|
||||
// Auto-determine Arduino usage if not explicitly set. Force this to 1
|
||||
// if you want to test Arduino path for audio input on a platform with
|
||||
// native audio support.
|
||||
#ifndef FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#if defined(ESP32) && !defined(ESP8266)
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 0
|
||||
#elif FL_HAS_INCLUDE(<Arduino.h>)
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 1
|
||||
#else
|
||||
#define FASTLED_USES_ARDUINO_AUDIO_INPUT 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#if defined(ESP32) && !defined(ESP8266)
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 1
|
||||
#else
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 0
|
||||
#endif
|
||||
#else
|
||||
#define FASTLED_USES_ESP32_AUDIO_INPUT 0
|
||||
#endif
|
||||
|
||||
|
||||
// Include ESP32 audio input implementation if on ESP32
|
||||
#if FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
#include "platforms/arduino/audio_input.hpp"
|
||||
#elif FASTLED_USES_ESP32_AUDIO_INPUT
|
||||
#include "platforms/esp/32/audio/audio_impl.hpp"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
#if FASTLED_USES_ARDUINO_AUDIO_INPUT
|
||||
// Use Arduino audio implementation
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
return arduino_create_audio_input(config, error_message);
|
||||
}
|
||||
#elif FASTLED_USES_ESP32_AUDIO_INPUT
|
||||
// ESP32 native implementation
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
return esp32_create_audio_input(config, error_message);
|
||||
}
|
||||
#else
|
||||
// Weak default implementation - no audio support
|
||||
FL_LINK_WEAK
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig &config, fl::string *error_message) {
|
||||
if (error_message) {
|
||||
*error_message = "AudioInput not supported on this platform.";
|
||||
}
|
||||
return fl::make_shared<fl::Null_Audio>();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Static method delegates to free function
|
||||
fl::shared_ptr<IAudioInput>
|
||||
IAudioInput::create(const AudioConfig &config, fl::string *error_message) {
|
||||
return platform_create_audio_input(config, error_message);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
142
libraries/FastLED/src/fl/audio_input.h
Normal file
142
libraries/FastLED/src/fl/audio_input.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "platforms/audio.h"
|
||||
|
||||
#ifndef FASTLED_HAS_AUDIO_INPUT
|
||||
#error "platforms/audio.h must define FASTLED_HAS_AUDIO_INPUT"
|
||||
#endif
|
||||
|
||||
|
||||
#define I2S_AUDIO_BUFFER_LEN 512
|
||||
#define AUDIO_DEFAULT_SAMPLE_RATE 44100ul
|
||||
#define AUDIO_DEFAULT_BIT_RESOLUTION 16
|
||||
#define AUDIO_DMA_BUFFER_COUNT 8
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Note: Right now these are esp specific, but they are designed to migrate to a common api.
|
||||
|
||||
enum AudioChannel {
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
Both = 2, // Two microphones can be used to capture both channels with one AudioSource.
|
||||
};
|
||||
|
||||
|
||||
enum I2SCommFormat {
|
||||
Philips = 0X01, // I2S communication I2S Philips standard, data launch at second BCK
|
||||
MSB = 0X02, // I2S communication MSB alignment standard, data launch at first BCK
|
||||
PCMShort = 0x04, // PCM Short standard, also known as DSP mode. The period of synchronization signal (WS) is 1 bck cycle.
|
||||
PCMLong = 0x0C, // PCM Long standard. The period of synchronization signal (WS) is channel_bit*bck cycles.
|
||||
Max = 0x0F, // standard max
|
||||
};
|
||||
|
||||
struct AudioConfigI2S {
|
||||
int mPinWs;
|
||||
int mPinSd;
|
||||
int mPinClk;
|
||||
int mI2sNum;
|
||||
AudioChannel mAudioChannel;
|
||||
u16 mSampleRate;
|
||||
u8 mBitResolution;
|
||||
I2SCommFormat mCommFormat;
|
||||
bool mInvert;
|
||||
AudioConfigI2S(
|
||||
int pin_ws,
|
||||
int pin_sd,
|
||||
int pin_clk,
|
||||
int i2s_num,
|
||||
AudioChannel mic_channel,
|
||||
u16 sample_rate,
|
||||
u8 bit_resolution,
|
||||
I2SCommFormat comm_format = Philips,
|
||||
bool invert = false
|
||||
)
|
||||
: mPinWs(pin_ws), mPinSd(pin_sd), mPinClk(pin_clk),
|
||||
mI2sNum(i2s_num), mAudioChannel(mic_channel),
|
||||
mSampleRate(sample_rate), mBitResolution(bit_resolution), mCommFormat(comm_format), mInvert(invert) {}
|
||||
};
|
||||
|
||||
struct AudioConfigPdm {
|
||||
int mPinDin;
|
||||
int mPinClk;
|
||||
int mI2sNum;
|
||||
u16 mSampleRate;
|
||||
bool mInvert = false;
|
||||
|
||||
AudioConfigPdm(int pin_din, int pin_clk, int i2s_num, u16 sample_rate = AUDIO_DEFAULT_SAMPLE_RATE, bool invert = false)
|
||||
: mPinDin(pin_din), mPinClk(pin_clk), mI2sNum(i2s_num), mSampleRate(sample_rate), mInvert(invert) {}
|
||||
};
|
||||
|
||||
|
||||
class AudioConfig : public fl::Variant<AudioConfigI2S, AudioConfigPdm> {
|
||||
public:
|
||||
// The most common microphone on Amazon as of 2025-September.
|
||||
static AudioConfig CreateInmp441(int pin_ws, int pin_sd, int pin_clk, AudioChannel channel, u16 sample_rate = 44100ul, int i2s_num = 0) {
|
||||
AudioConfigI2S config(pin_ws, pin_sd, pin_clk, i2s_num, channel, sample_rate, 16);
|
||||
return AudioConfig(config);
|
||||
}
|
||||
AudioConfig(const AudioConfigI2S& config) : fl::Variant<AudioConfigI2S, AudioConfigPdm>(config) {}
|
||||
AudioConfig(const AudioConfigPdm& config) : fl::Variant<AudioConfigI2S, AudioConfigPdm>(config) {}
|
||||
};
|
||||
|
||||
class IAudioInput {
|
||||
public:
|
||||
// This is the single factory function for creating the audio source. If the creation was successful, then
|
||||
// the return value will be non-null. If the creation was not successful, then the return value will be null
|
||||
// and the error_message will be set to a non-empty string.
|
||||
// Keep in mind that the AudioConfig is a variant type. Many esp types do not support all the types in the variant.
|
||||
// For example, the AudioConfigPdm is not supported on the ESP32-C3 and in this case it will return a null pointer
|
||||
// and the error_message will be set to a non-empty string.
|
||||
// Implimentation notes:
|
||||
// It's very important that the implimentation uses a esp task / interrupt to fill in the buffer. The reason is that
|
||||
// there will be looooong delays during FastLED show() on some esp platforms, for example idf 4.4. If we do
|
||||
// poll only, then audio buffers can be dropped. However if using a task then the audio buffers will be
|
||||
// set internally via an interrupt / queue and then they can just be popped off the queue.
|
||||
static fl::shared_ptr<IAudioInput> create(const AudioConfig& config, fl::string* error_message = nullptr);
|
||||
|
||||
|
||||
virtual ~IAudioInput() = default;
|
||||
// Starts the audio source.
|
||||
virtual void start() = 0;
|
||||
// Stops the audio source, call this before light sleep.
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual bool error(fl::string* msg = nullptr) = 0; // if an error occured then query it here.
|
||||
// Read audio data and return as AudioSample with calculated timestamp.
|
||||
// Returns invalid AudioSample on error or when no data is available.
|
||||
virtual AudioSample read() = 0;
|
||||
|
||||
// Read all available audio data and return as AudioSample. All AudioSamples
|
||||
// returned by this will be valid.
|
||||
size_t readAll(fl::vector_inlined<AudioSample, 16> *out) {
|
||||
size_t count = 0;
|
||||
while (true) {
|
||||
AudioSample sample = read();
|
||||
if (sample.isValid()) {
|
||||
out->push_back(sample);
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Free function for audio input creation - can be overridden by platform-specific implementations
|
||||
fl::shared_ptr<IAudioInput> platform_create_audio_input(const AudioConfig& config, fl::string* error_message = nullptr) FL_LINK_WEAK;
|
||||
|
||||
} // namespace fl
|
||||
759
libraries/FastLED/src/fl/audio_reactive.cpp
Normal file
759
libraries/FastLED/src/fl/audio_reactive.cpp
Normal file
@@ -0,0 +1,759 @@
|
||||
#include "fl/audio_reactive.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/memory.h"
|
||||
#include <math.h>
|
||||
|
||||
namespace fl {
|
||||
|
||||
AudioReactive::AudioReactive()
|
||||
: mConfig{}, mFFTBins(16) // Initialize with 16 frequency bins
|
||||
{
|
||||
// Initialize enhanced beat detection components
|
||||
mSpectralFluxDetector = fl::make_unique<SpectralFluxDetector>();
|
||||
mPerceptualWeighting = fl::make_unique<PerceptualWeighting>();
|
||||
|
||||
// Initialize previous magnitudes array to zero
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
AudioReactive::~AudioReactive() = default;
|
||||
|
||||
void AudioReactive::begin(const AudioReactiveConfig& config) {
|
||||
setConfig(config);
|
||||
|
||||
// Reset state
|
||||
mCurrentData = AudioData{};
|
||||
mSmoothedData = AudioData{};
|
||||
mLastBeatTime = 0;
|
||||
mPreviousVolume = 0.0f;
|
||||
mAGCMultiplier = 1.0f;
|
||||
mMaxSample = 0.0f;
|
||||
mAverageLevel = 0.0f;
|
||||
|
||||
// Reset enhanced beat detection components
|
||||
if (mSpectralFluxDetector) {
|
||||
mSpectralFluxDetector->reset();
|
||||
mSpectralFluxDetector->setThreshold(config.spectralFluxThreshold);
|
||||
}
|
||||
|
||||
// Reset previous magnitudes
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::setConfig(const AudioReactiveConfig& config) {
|
||||
mConfig = config;
|
||||
}
|
||||
|
||||
void AudioReactive::processSample(const AudioSample& sample) {
|
||||
if (!sample.isValid()) {
|
||||
return; // Invalid sample, ignore
|
||||
}
|
||||
|
||||
// Extract timestamp from the AudioSample
|
||||
fl::u32 currentTimeMs = sample.timestamp();
|
||||
|
||||
// Process the AudioSample immediately - timing is gated by sample availability
|
||||
processFFT(sample);
|
||||
updateVolumeAndPeak(sample);
|
||||
|
||||
// Enhanced processing pipeline
|
||||
calculateBandEnergies();
|
||||
updateSpectralFlux();
|
||||
|
||||
// Enhanced beat detection (includes original)
|
||||
detectBeat(currentTimeMs);
|
||||
detectEnhancedBeats(currentTimeMs);
|
||||
|
||||
// Apply perceptual weighting if enabled
|
||||
applyPerceptualWeighting();
|
||||
|
||||
applyGain();
|
||||
applyScaling();
|
||||
smoothResults();
|
||||
|
||||
mCurrentData.timestamp = currentTimeMs;
|
||||
}
|
||||
|
||||
void AudioReactive::update(fl::u32 currentTimeMs) {
|
||||
// This method handles updates without new sample data
|
||||
// Just apply smoothing and update timestamp
|
||||
smoothResults();
|
||||
mCurrentData.timestamp = currentTimeMs;
|
||||
}
|
||||
|
||||
void AudioReactive::processFFT(const AudioSample& sample) {
|
||||
// Get PCM data from AudioSample
|
||||
const auto& pcmData = sample.pcm();
|
||||
if (pcmData.empty()) return;
|
||||
|
||||
// Use AudioSample's built-in FFT capability
|
||||
sample.fft(&mFFTBins);
|
||||
|
||||
// Map FFT bins to frequency channels using WLED-compatible mapping
|
||||
mapFFTBinsToFrequencyChannels();
|
||||
}
|
||||
|
||||
void AudioReactive::mapFFTBinsToFrequencyChannels() {
|
||||
// Copy FFT results to frequency bins array
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
|
||||
mCurrentData.frequencyBins[i] = mFFTBins.bins_raw[i];
|
||||
} else {
|
||||
mCurrentData.frequencyBins[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pink noise compensation (from WLED)
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= PINK_NOISE_COMPENSATION[i];
|
||||
}
|
||||
|
||||
// Find dominant frequency
|
||||
float maxMagnitude = 0.0f;
|
||||
int maxBin = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (mCurrentData.frequencyBins[i] > maxMagnitude) {
|
||||
maxMagnitude = mCurrentData.frequencyBins[i];
|
||||
maxBin = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert bin index to approximate frequency
|
||||
// Rough approximation based on WLED frequency mapping
|
||||
const float binCenterFrequencies[16] = {
|
||||
64.5f, // Bin 0: 43-86 Hz
|
||||
107.5f, // Bin 1: 86-129 Hz
|
||||
172.5f, // Bin 2: 129-216 Hz
|
||||
258.5f, // Bin 3: 216-301 Hz
|
||||
365.5f, // Bin 4: 301-430 Hz
|
||||
495.0f, // Bin 5: 430-560 Hz
|
||||
689.0f, // Bin 6: 560-818 Hz
|
||||
969.0f, // Bin 7: 818-1120 Hz
|
||||
1270.5f, // Bin 8: 1120-1421 Hz
|
||||
1658.0f, // Bin 9: 1421-1895 Hz
|
||||
2153.5f, // Bin 10: 1895-2412 Hz
|
||||
2713.5f, // Bin 11: 2412-3015 Hz
|
||||
3359.5f, // Bin 12: 3015-3704 Hz
|
||||
4091.5f, // Bin 13: 3704-4479 Hz
|
||||
5792.5f, // Bin 14: 4479-7106 Hz
|
||||
8182.5f // Bin 15: 7106-9259 Hz
|
||||
};
|
||||
|
||||
mCurrentData.dominantFrequency = binCenterFrequencies[maxBin];
|
||||
mCurrentData.magnitude = maxMagnitude;
|
||||
}
|
||||
|
||||
void AudioReactive::updateVolumeAndPeak(const AudioSample& sample) {
|
||||
// Get PCM data from AudioSample
|
||||
const auto& pcmData = sample.pcm();
|
||||
if (pcmData.empty()) {
|
||||
mCurrentData.volume = 0.0f;
|
||||
mCurrentData.volumeRaw = 0.0f;
|
||||
mCurrentData.peak = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Use AudioSample's built-in RMS calculation
|
||||
float rms = sample.rms();
|
||||
|
||||
// Calculate peak from PCM data
|
||||
float maxSample = 0.0f;
|
||||
for (fl::i16 pcmSample : pcmData) {
|
||||
float absSample = (pcmSample < 0) ? -pcmSample : pcmSample;
|
||||
maxSample = (maxSample > absSample) ? maxSample : absSample;
|
||||
}
|
||||
|
||||
// Scale to 0-255 range (approximately)
|
||||
mCurrentData.volumeRaw = rms / 128.0f; // Rough scaling
|
||||
mCurrentData.volume = mCurrentData.volumeRaw;
|
||||
|
||||
// Peak detection
|
||||
mCurrentData.peak = maxSample / 32768.0f * 255.0f;
|
||||
|
||||
// Update AGC tracking
|
||||
if (mConfig.agcEnabled) {
|
||||
// AGC with attack/decay behavior
|
||||
float agcAttackRate = mConfig.attack / 255.0f * 0.2f + 0.01f; // 0.01 to 0.21
|
||||
float agcDecayRate = mConfig.decay / 255.0f * 0.05f + 0.001f; // 0.001 to 0.051
|
||||
|
||||
// Track maximum level with attack/decay
|
||||
if (maxSample > mMaxSample) {
|
||||
// Rising - use attack rate (faster response)
|
||||
mMaxSample = mMaxSample * (1.0f - agcAttackRate) + maxSample * agcAttackRate;
|
||||
} else {
|
||||
// Falling - use decay rate (slower response)
|
||||
mMaxSample = mMaxSample * (1.0f - agcDecayRate) + maxSample * agcDecayRate;
|
||||
}
|
||||
|
||||
// Update AGC multiplier with proper bounds
|
||||
if (mMaxSample > 1000.0f) {
|
||||
float targetLevel = 16384.0f; // Half of full scale
|
||||
float newMultiplier = targetLevel / mMaxSample;
|
||||
|
||||
// Smooth AGC multiplier changes using attack/decay
|
||||
if (newMultiplier > mAGCMultiplier) {
|
||||
// Increasing gain - use attack rate
|
||||
mAGCMultiplier = mAGCMultiplier * (1.0f - agcAttackRate) + newMultiplier * agcAttackRate;
|
||||
} else {
|
||||
// Decreasing gain - use decay rate
|
||||
mAGCMultiplier = mAGCMultiplier * (1.0f - agcDecayRate) + newMultiplier * agcDecayRate;
|
||||
}
|
||||
|
||||
// Clamp multiplier to reasonable bounds
|
||||
mAGCMultiplier = (mAGCMultiplier < 0.1f) ? 0.1f : ((mAGCMultiplier > 10.0f) ? 10.0f : mAGCMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::detectBeat(fl::u32 currentTimeMs) {
|
||||
// Need minimum time since last beat
|
||||
if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
|
||||
mCurrentData.beatDetected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple beat detection based on volume increase
|
||||
float currentVolume = mCurrentData.volume;
|
||||
|
||||
// Beat detected if volume significantly increased
|
||||
if (currentVolume > mPreviousVolume + mVolumeThreshold &&
|
||||
currentVolume > 5.0f) { // Minimum volume threshold
|
||||
mCurrentData.beatDetected = true;
|
||||
mLastBeatTime = currentTimeMs;
|
||||
} else {
|
||||
mCurrentData.beatDetected = false;
|
||||
}
|
||||
|
||||
// Update previous volume for next comparison using attack/decay
|
||||
float beatAttackRate = mConfig.attack / 255.0f * 0.5f + 0.1f; // 0.1 to 0.6
|
||||
float beatDecayRate = mConfig.decay / 255.0f * 0.3f + 0.05f; // 0.05 to 0.35
|
||||
|
||||
if (currentVolume > mPreviousVolume) {
|
||||
// Rising volume - use attack rate (faster tracking)
|
||||
mPreviousVolume = mPreviousVolume * (1.0f - beatAttackRate) + currentVolume * beatAttackRate;
|
||||
} else {
|
||||
// Falling volume - use decay rate (slower tracking)
|
||||
mPreviousVolume = mPreviousVolume * (1.0f - beatDecayRate) + currentVolume * beatDecayRate;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyGain() {
|
||||
// Apply gain setting (0-255 maps to 0.0-2.0 multiplier)
|
||||
float gainMultiplier = static_cast<float>(mConfig.gain) / 128.0f;
|
||||
|
||||
mCurrentData.volume *= gainMultiplier;
|
||||
mCurrentData.volumeRaw *= gainMultiplier;
|
||||
mCurrentData.peak *= gainMultiplier;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= gainMultiplier;
|
||||
}
|
||||
|
||||
// Apply AGC if enabled
|
||||
if (mConfig.agcEnabled) {
|
||||
mCurrentData.volume *= mAGCMultiplier;
|
||||
mCurrentData.volumeRaw *= mAGCMultiplier;
|
||||
mCurrentData.peak *= mAGCMultiplier;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mCurrentData.frequencyBins[i] *= mAGCMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyScaling() {
|
||||
// Apply scaling mode to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
float value = mCurrentData.frequencyBins[i];
|
||||
|
||||
switch (mConfig.scalingMode) {
|
||||
case 1: // Logarithmic scaling
|
||||
if (value > 1.0f) {
|
||||
value = logf(value) * 20.0f; // Scale factor
|
||||
} else {
|
||||
value = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Linear scaling (no change)
|
||||
// value remains as-is
|
||||
break;
|
||||
|
||||
case 3: // Square root scaling
|
||||
if (value > 0.0f) {
|
||||
value = sqrtf(value) * 8.0f; // Scale factor
|
||||
} else {
|
||||
value = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0: // No scaling
|
||||
default:
|
||||
// value remains as-is
|
||||
break;
|
||||
}
|
||||
|
||||
mCurrentData.frequencyBins[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::smoothResults() {
|
||||
// Attack/decay smoothing - different rates for rising vs falling values
|
||||
// Convert attack/decay times to smoothing factors
|
||||
// Shorter times = less smoothing (faster response)
|
||||
float attackFactor = 1.0f - (mConfig.attack / 255.0f * 0.9f); // Range: 0.1 to 1.0
|
||||
float decayFactor = 1.0f - (mConfig.decay / 255.0f * 0.95f); // Range: 0.05 to 1.0
|
||||
|
||||
// Apply attack/decay smoothing to volume
|
||||
if (mCurrentData.volume > mSmoothedData.volume) {
|
||||
// Rising - use attack time (faster response)
|
||||
mSmoothedData.volume = mSmoothedData.volume * (1.0f - attackFactor) +
|
||||
mCurrentData.volume * attackFactor;
|
||||
} else {
|
||||
// Falling - use decay time (slower response)
|
||||
mSmoothedData.volume = mSmoothedData.volume * (1.0f - decayFactor) +
|
||||
mCurrentData.volume * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to volumeRaw
|
||||
if (mCurrentData.volumeRaw > mSmoothedData.volumeRaw) {
|
||||
mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - attackFactor) +
|
||||
mCurrentData.volumeRaw * attackFactor;
|
||||
} else {
|
||||
mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - decayFactor) +
|
||||
mCurrentData.volumeRaw * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to peak
|
||||
if (mCurrentData.peak > mSmoothedData.peak) {
|
||||
mSmoothedData.peak = mSmoothedData.peak * (1.0f - attackFactor) +
|
||||
mCurrentData.peak * attackFactor;
|
||||
} else {
|
||||
mSmoothedData.peak = mSmoothedData.peak * (1.0f - decayFactor) +
|
||||
mCurrentData.peak * decayFactor;
|
||||
}
|
||||
|
||||
// Apply attack/decay smoothing to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (mCurrentData.frequencyBins[i] > mSmoothedData.frequencyBins[i]) {
|
||||
// Rising - use attack time
|
||||
mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - attackFactor) +
|
||||
mCurrentData.frequencyBins[i] * attackFactor;
|
||||
} else {
|
||||
// Falling - use decay time
|
||||
mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - decayFactor) +
|
||||
mCurrentData.frequencyBins[i] * decayFactor;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy non-smoothed values
|
||||
mSmoothedData.beatDetected = mCurrentData.beatDetected;
|
||||
mSmoothedData.dominantFrequency = mCurrentData.dominantFrequency;
|
||||
mSmoothedData.magnitude = mCurrentData.magnitude;
|
||||
mSmoothedData.timestamp = mCurrentData.timestamp;
|
||||
}
|
||||
|
||||
const AudioData& AudioReactive::getData() const {
|
||||
return mCurrentData;
|
||||
}
|
||||
|
||||
const AudioData& AudioReactive::getSmoothedData() const {
|
||||
return mSmoothedData;
|
||||
}
|
||||
|
||||
float AudioReactive::getVolume() const {
|
||||
return mCurrentData.volume;
|
||||
}
|
||||
|
||||
float AudioReactive::getBass() const {
|
||||
// Average of bins 0-1 (sub-bass and bass)
|
||||
return (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
|
||||
}
|
||||
|
||||
float AudioReactive::getMid() const {
|
||||
// Average of bins 6-7 (midrange around 1kHz)
|
||||
return (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
|
||||
}
|
||||
|
||||
float AudioReactive::getTreble() const {
|
||||
// Average of bins 14-15 (high frequencies)
|
||||
return (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
|
||||
}
|
||||
|
||||
bool AudioReactive::isBeat() const {
|
||||
return mCurrentData.beatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isBassBeat() const {
|
||||
return mCurrentData.bassBeatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isMidBeat() const {
|
||||
return mCurrentData.midBeatDetected;
|
||||
}
|
||||
|
||||
bool AudioReactive::isTrebleBeat() const {
|
||||
return mCurrentData.trebleBeatDetected;
|
||||
}
|
||||
|
||||
float AudioReactive::getSpectralFlux() const {
|
||||
return mCurrentData.spectralFlux;
|
||||
}
|
||||
|
||||
float AudioReactive::getBassEnergy() const {
|
||||
return mCurrentData.bassEnergy;
|
||||
}
|
||||
|
||||
float AudioReactive::getMidEnergy() const {
|
||||
return mCurrentData.midEnergy;
|
||||
}
|
||||
|
||||
float AudioReactive::getTrebleEnergy() const {
|
||||
return mCurrentData.trebleEnergy;
|
||||
}
|
||||
|
||||
fl::u8 AudioReactive::volumeToScale255() const {
|
||||
float vol = (mCurrentData.volume < 0.0f) ? 0.0f : ((mCurrentData.volume > 255.0f) ? 255.0f : mCurrentData.volume);
|
||||
return static_cast<fl::u8>(vol);
|
||||
}
|
||||
|
||||
CRGB AudioReactive::volumeToColor(const CRGBPalette16& /* palette */) const {
|
||||
fl::u8 index = volumeToScale255();
|
||||
// Simplified color palette lookup
|
||||
return CRGB(index, index, index); // For now, return grayscale
|
||||
}
|
||||
|
||||
fl::u8 AudioReactive::frequencyToScale255(fl::u8 binIndex) const {
|
||||
if (binIndex >= 16) return 0;
|
||||
|
||||
float value = (mCurrentData.frequencyBins[binIndex] < 0.0f) ? 0.0f :
|
||||
((mCurrentData.frequencyBins[binIndex] > 255.0f) ? 255.0f : mCurrentData.frequencyBins[binIndex]);
|
||||
return static_cast<fl::u8>(value);
|
||||
}
|
||||
|
||||
// Enhanced beat detection methods
|
||||
void AudioReactive::calculateBandEnergies() {
|
||||
// Calculate energy for bass frequencies (bins 0-1)
|
||||
mCurrentData.bassEnergy = (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
|
||||
|
||||
// Calculate energy for mid frequencies (bins 6-7)
|
||||
mCurrentData.midEnergy = (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
|
||||
|
||||
// Calculate energy for treble frequencies (bins 14-15)
|
||||
mCurrentData.trebleEnergy = (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
|
||||
}
|
||||
|
||||
void AudioReactive::updateSpectralFlux() {
|
||||
if (!mSpectralFluxDetector) {
|
||||
mCurrentData.spectralFlux = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate spectral flux from current and previous frequency bins
|
||||
mCurrentData.spectralFlux = mSpectralFluxDetector->calculateSpectralFlux(
|
||||
mCurrentData.frequencyBins,
|
||||
mPreviousMagnitudes.data()
|
||||
);
|
||||
|
||||
// Update previous magnitudes for next frame
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mPreviousMagnitudes[i] = mCurrentData.frequencyBins[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::detectEnhancedBeats(fl::u32 currentTimeMs) {
|
||||
// Reset beat flags
|
||||
mCurrentData.bassBeatDetected = false;
|
||||
mCurrentData.midBeatDetected = false;
|
||||
mCurrentData.trebleBeatDetected = false;
|
||||
|
||||
// Skip if enhanced beat detection is disabled
|
||||
if (!mConfig.enableSpectralFlux && !mConfig.enableMultiBand) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need minimum time since last beat for enhanced detection too
|
||||
if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spectral flux-based beat detection
|
||||
if (mConfig.enableSpectralFlux && mSpectralFluxDetector) {
|
||||
bool onsetDetected = mSpectralFluxDetector->detectOnset(
|
||||
mCurrentData.frequencyBins,
|
||||
mPreviousMagnitudes.data()
|
||||
);
|
||||
|
||||
if (onsetDetected) {
|
||||
// Enhance the traditional beat detection when spectral flux confirms
|
||||
mCurrentData.beatDetected = true;
|
||||
mLastBeatTime = currentTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-band beat detection
|
||||
if (mConfig.enableMultiBand) {
|
||||
// Bass beat detection (bins 0-1)
|
||||
if (mCurrentData.bassEnergy > mConfig.bassThreshold) {
|
||||
mCurrentData.bassBeatDetected = true;
|
||||
}
|
||||
|
||||
// Mid beat detection (bins 6-7)
|
||||
if (mCurrentData.midEnergy > mConfig.midThreshold) {
|
||||
mCurrentData.midBeatDetected = true;
|
||||
}
|
||||
|
||||
// Treble beat detection (bins 14-15)
|
||||
if (mCurrentData.trebleEnergy > mConfig.trebleThreshold) {
|
||||
mCurrentData.trebleBeatDetected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioReactive::applyPerceptualWeighting() {
|
||||
// Apply perceptual weighting if available
|
||||
if (mPerceptualWeighting) {
|
||||
mPerceptualWeighting->applyAWeighting(mCurrentData);
|
||||
|
||||
// Apply loudness compensation with reference level of 50.0f
|
||||
mPerceptualWeighting->applyLoudnessCompensation(mCurrentData, 50.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
float AudioReactive::mapFrequencyBin(int fromBin, int toBin) {
|
||||
if (fromBin < 0 || toBin >= static_cast<int>(mFFTBins.size()) || fromBin > toBin) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float sum = 0.0f;
|
||||
for (int i = fromBin; i <= toBin; ++i) {
|
||||
if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
|
||||
sum += mFFTBins.bins_raw[i];
|
||||
}
|
||||
}
|
||||
|
||||
return sum / static_cast<float>(toBin - fromBin + 1);
|
||||
}
|
||||
|
||||
float AudioReactive::computeRMS(const fl::vector<fl::i16>& samples) {
|
||||
if (samples.empty()) return 0.0f;
|
||||
|
||||
float sumSquares = 0.0f;
|
||||
for (const auto& sample : samples) {
|
||||
float f = static_cast<float>(sample);
|
||||
sumSquares += f * f;
|
||||
}
|
||||
|
||||
return sqrtf(sumSquares / samples.size());
|
||||
}
|
||||
|
||||
// SpectralFluxDetector implementation
|
||||
SpectralFluxDetector::SpectralFluxDetector()
|
||||
: mFluxThreshold(0.1f)
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
, mHistoryIndex(0)
|
||||
#endif
|
||||
{
|
||||
// Initialize previous magnitudes to zero
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Initialize flux history to zero
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
mFluxHistory[i] = 0.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SpectralFluxDetector::~SpectralFluxDetector() = default;
|
||||
|
||||
void SpectralFluxDetector::reset() {
|
||||
for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
|
||||
mPreviousMagnitudes[i] = 0.0f;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
mFluxHistory[i] = 0.0f;
|
||||
}
|
||||
mHistoryIndex = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SpectralFluxDetector::detectOnset(const float* currentBins, const float* /* previousBins */) {
|
||||
float flux = calculateSpectralFlux(currentBins, mPreviousMagnitudes.data());
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Store flux in history for adaptive threshold calculation
|
||||
mFluxHistory[mHistoryIndex] = flux;
|
||||
mHistoryIndex = (mHistoryIndex + 1) % mFluxHistory.size();
|
||||
|
||||
float adaptiveThreshold = calculateAdaptiveThreshold();
|
||||
return flux > adaptiveThreshold;
|
||||
#else
|
||||
// Simple fixed threshold for memory-constrained platforms
|
||||
return flux > mFluxThreshold;
|
||||
#endif
|
||||
}
|
||||
|
||||
float SpectralFluxDetector::calculateSpectralFlux(const float* currentBins, const float* previousBins) {
|
||||
float flux = 0.0f;
|
||||
|
||||
// Calculate spectral flux as sum of positive differences
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
float diff = currentBins[i] - previousBins[i];
|
||||
if (diff > 0.0f) {
|
||||
flux += diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous magnitudes for next calculation
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mPreviousMagnitudes[i] = currentBins[i];
|
||||
}
|
||||
|
||||
return flux;
|
||||
}
|
||||
|
||||
void SpectralFluxDetector::setThreshold(float threshold) {
|
||||
mFluxThreshold = threshold;
|
||||
}
|
||||
|
||||
float SpectralFluxDetector::getThreshold() const {
|
||||
return mFluxThreshold;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
float SpectralFluxDetector::calculateAdaptiveThreshold() {
|
||||
// Calculate moving average of flux history
|
||||
float sum = 0.0f;
|
||||
for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
|
||||
sum += mFluxHistory[i];
|
||||
}
|
||||
float average = sum / mFluxHistory.size();
|
||||
|
||||
// Adaptive threshold is base threshold plus some multiple of recent average
|
||||
return mFluxThreshold + (average * 0.5f);
|
||||
}
|
||||
#endif
|
||||
|
||||
// BeatDetectors implementation
|
||||
BeatDetectors::BeatDetectors()
|
||||
: mBassEnergy(0.0f), mMidEnergy(0.0f), mTrebleEnergy(0.0f)
|
||||
, mPreviousBassEnergy(0.0f), mPreviousMidEnergy(0.0f), mPreviousTrebleEnergy(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
BeatDetectors::~BeatDetectors() = default;
|
||||
|
||||
void BeatDetectors::reset() {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
bass.reset();
|
||||
mid.reset();
|
||||
treble.reset();
|
||||
#else
|
||||
combined.reset();
|
||||
#endif
|
||||
|
||||
mBassEnergy = 0.0f;
|
||||
mMidEnergy = 0.0f;
|
||||
mTrebleEnergy = 0.0f;
|
||||
mPreviousBassEnergy = 0.0f;
|
||||
mPreviousMidEnergy = 0.0f;
|
||||
mPreviousTrebleEnergy = 0.0f;
|
||||
}
|
||||
|
||||
void BeatDetectors::detectBeats(const float* frequencyBins, AudioData& audioData) {
|
||||
// Calculate current band energies
|
||||
mBassEnergy = (frequencyBins[0] + frequencyBins[1]) / 2.0f;
|
||||
mMidEnergy = (frequencyBins[6] + frequencyBins[7]) / 2.0f;
|
||||
mTrebleEnergy = (frequencyBins[14] + frequencyBins[15]) / 2.0f;
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Use separate detectors for each band
|
||||
audioData.bassBeatDetected = bass.detectOnset(&mBassEnergy, &mPreviousBassEnergy);
|
||||
audioData.midBeatDetected = mid.detectOnset(&mMidEnergy, &mPreviousMidEnergy);
|
||||
audioData.trebleBeatDetected = treble.detectOnset(&mTrebleEnergy, &mPreviousTrebleEnergy);
|
||||
#else
|
||||
// Use simple threshold detection for memory-constrained platforms
|
||||
audioData.bassBeatDetected = (mBassEnergy > mPreviousBassEnergy * 1.3f) && (mBassEnergy > 0.1f);
|
||||
audioData.midBeatDetected = (mMidEnergy > mPreviousMidEnergy * 1.25f) && (mMidEnergy > 0.08f);
|
||||
audioData.trebleBeatDetected = (mTrebleEnergy > mPreviousTrebleEnergy * 1.2f) && (mTrebleEnergy > 0.05f);
|
||||
#endif
|
||||
|
||||
// Update previous energies
|
||||
mPreviousBassEnergy = mBassEnergy;
|
||||
mPreviousMidEnergy = mMidEnergy;
|
||||
mPreviousTrebleEnergy = mTrebleEnergy;
|
||||
}
|
||||
|
||||
void BeatDetectors::setThresholds(float bassThresh, float midThresh, float trebleThresh) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
bass.setThreshold(bassThresh);
|
||||
mid.setThreshold(midThresh);
|
||||
treble.setThreshold(trebleThresh);
|
||||
#else
|
||||
combined.setThreshold((bassThresh + midThresh + trebleThresh) / 3.0f);
|
||||
#endif
|
||||
}
|
||||
|
||||
// PerceptualWeighting implementation
|
||||
PerceptualWeighting::PerceptualWeighting()
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
: mHistoryIndex(0)
|
||||
#endif
|
||||
{
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Initialize loudness history to zero
|
||||
for (fl::size i = 0; i < mLoudnessHistory.size(); ++i) {
|
||||
mLoudnessHistory[i] = 0.0f;
|
||||
}
|
||||
// Suppress unused warning until mHistoryIndex is implemented
|
||||
(void)mHistoryIndex;
|
||||
#endif
|
||||
}
|
||||
|
||||
PerceptualWeighting::~PerceptualWeighting() = default;
|
||||
|
||||
void PerceptualWeighting::applyAWeighting(AudioData& data) const {
|
||||
// Apply A-weighting coefficients to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
data.frequencyBins[i] *= A_WEIGHTING_COEFFS[i];
|
||||
}
|
||||
}
|
||||
|
||||
void PerceptualWeighting::applyLoudnessCompensation(AudioData& data, float referenceLevel) const {
|
||||
// Calculate current loudness level
|
||||
float currentLoudness = data.volume;
|
||||
|
||||
// Calculate compensation factor based on difference from reference
|
||||
float compensationFactor = 1.0f;
|
||||
if (currentLoudness < referenceLevel) {
|
||||
// Boost quiet signals
|
||||
compensationFactor = 1.0f + (referenceLevel - currentLoudness) / referenceLevel * 0.3f;
|
||||
} else if (currentLoudness > referenceLevel * 1.5f) {
|
||||
// Slightly reduce very loud signals
|
||||
compensationFactor = 1.0f - (currentLoudness - referenceLevel * 1.5f) / (referenceLevel * 2.0f) * 0.2f;
|
||||
}
|
||||
|
||||
// Apply compensation to frequency bins
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
data.frequencyBins[i] *= compensationFactor;
|
||||
}
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Store in history for future adaptive compensation (not implemented yet)
|
||||
// This would be used for more sophisticated dynamic range compensation
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
231
libraries/FastLED/src/fl/audio_reactive.h
Normal file
231
libraries/FastLED/src/fl/audio_reactive.h
Normal file
@@ -0,0 +1,231 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/array.h"
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/colorutils.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations for enhanced beat detection
|
||||
class SpectralFluxDetector;
|
||||
class PerceptualWeighting;
|
||||
|
||||
// Audio data structure - matches original WLED output with extensions
|
||||
struct AudioData {
|
||||
float volume = 0.0f; // Overall volume level (0-255)
|
||||
float volumeRaw = 0.0f; // Raw volume without smoothing
|
||||
float peak = 0.0f; // Peak level (0-255)
|
||||
bool beatDetected = false; // Beat detection flag
|
||||
float frequencyBins[16] = {0}; // 16 frequency bins (matches WLED NUM_GEQ_CHANNELS)
|
||||
float dominantFrequency = 0.0f; // Major peak frequency (Hz)
|
||||
float magnitude = 0.0f; // FFT magnitude of dominant frequency
|
||||
fl::u32 timestamp = 0; // millis() when data was captured
|
||||
|
||||
// Enhanced beat detection fields
|
||||
bool bassBeatDetected = false; // Bass-specific beat detection
|
||||
bool midBeatDetected = false; // Mid-range beat detection
|
||||
bool trebleBeatDetected = false; // Treble beat detection
|
||||
float spectralFlux = 0.0f; // Current spectral flux value
|
||||
float bassEnergy = 0.0f; // Energy in bass frequencies (0-1)
|
||||
float midEnergy = 0.0f; // Energy in mid frequencies (6-7)
|
||||
float trebleEnergy = 0.0f; // Energy in treble frequencies (14-15)
|
||||
};
|
||||
|
||||
struct AudioReactiveConfig {
|
||||
fl::u8 gain = 128; // Input gain (0-255)
|
||||
fl::u8 sensitivity = 128; // AGC sensitivity
|
||||
bool agcEnabled = true; // Auto gain control
|
||||
bool noiseGate = true; // Noise gate
|
||||
fl::u8 attack = 50; // Attack time (ms) - how fast to respond to increases
|
||||
fl::u8 decay = 200; // Decay time (ms) - how slow to respond to decreases
|
||||
u16 sampleRate = 22050; // Sample rate (Hz)
|
||||
fl::u8 scalingMode = 3; // 0=none, 1=log, 2=linear, 3=sqrt
|
||||
|
||||
// Enhanced beat detection configuration
|
||||
bool enableSpectralFlux = true; // Enable spectral flux-based beat detection
|
||||
bool enableMultiBand = true; // Enable multi-band beat detection
|
||||
float spectralFluxThreshold = 0.1f; // Threshold for spectral flux detection
|
||||
float bassThreshold = 0.15f; // Threshold for bass beat detection
|
||||
float midThreshold = 0.12f; // Threshold for mid beat detection
|
||||
float trebleThreshold = 0.08f; // Threshold for treble beat detection
|
||||
};
|
||||
|
||||
class AudioReactive {
|
||||
public:
|
||||
AudioReactive();
|
||||
~AudioReactive();
|
||||
|
||||
// Setup
|
||||
void begin(const AudioReactiveConfig& config = AudioReactiveConfig{});
|
||||
void setConfig(const AudioReactiveConfig& config);
|
||||
|
||||
// Process audio sample - this does all the work immediately
|
||||
void processSample(const AudioSample& sample);
|
||||
|
||||
// Optional: update smoothing without new sample data
|
||||
void update(fl::u32 currentTimeMs);
|
||||
|
||||
// Data access
|
||||
const AudioData& getData() const;
|
||||
const AudioData& getSmoothedData() const;
|
||||
|
||||
// Convenience accessors
|
||||
float getVolume() const;
|
||||
float getBass() const; // Average of bins 0-1
|
||||
float getMid() const; // Average of bins 6-7
|
||||
float getTreble() const; // Average of bins 14-15
|
||||
bool isBeat() const;
|
||||
|
||||
// Enhanced beat detection accessors
|
||||
bool isBassBeat() const;
|
||||
bool isMidBeat() const;
|
||||
bool isTrebleBeat() const;
|
||||
float getSpectralFlux() const;
|
||||
float getBassEnergy() const;
|
||||
float getMidEnergy() const;
|
||||
float getTrebleEnergy() const;
|
||||
|
||||
// Effect helpers
|
||||
fl::u8 volumeToScale255() const;
|
||||
CRGB volumeToColor(const CRGBPalette16& palette) const;
|
||||
fl::u8 frequencyToScale255(fl::u8 binIndex) const;
|
||||
|
||||
private:
|
||||
// Internal processing methods
|
||||
void processFFT(const AudioSample& sample);
|
||||
void mapFFTBinsToFrequencyChannels();
|
||||
void updateVolumeAndPeak(const AudioSample& sample);
|
||||
void detectBeat(fl::u32 currentTimeMs);
|
||||
void smoothResults();
|
||||
void applyScaling();
|
||||
void applyGain();
|
||||
|
||||
// Enhanced beat detection methods
|
||||
void detectEnhancedBeats(fl::u32 currentTimeMs);
|
||||
void calculateBandEnergies();
|
||||
void updateSpectralFlux();
|
||||
void applyPerceptualWeighting();
|
||||
|
||||
// Helper methods
|
||||
float mapFrequencyBin(int fromBin, int toBin);
|
||||
float computeRMS(const fl::vector<fl::i16>& samples);
|
||||
|
||||
// Configuration
|
||||
AudioReactiveConfig mConfig;
|
||||
|
||||
// FFT processing
|
||||
FFT mFFT;
|
||||
FFTBins mFFTBins;
|
||||
|
||||
// Audio data
|
||||
AudioData mCurrentData;
|
||||
AudioData mSmoothedData;
|
||||
|
||||
// Processing state
|
||||
fl::u32 mLastBeatTime = 0;
|
||||
static constexpr fl::u32 BEAT_COOLDOWN = 100; // 100ms minimum between beats
|
||||
|
||||
// Volume tracking for beat detection
|
||||
float mPreviousVolume = 0.0f;
|
||||
float mVolumeThreshold = 10.0f;
|
||||
|
||||
// Pink noise compensation (from WLED)
|
||||
static constexpr float PINK_NOISE_COMPENSATION[16] = {
|
||||
1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f,
|
||||
1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f
|
||||
};
|
||||
|
||||
// AGC state
|
||||
float mAGCMultiplier = 1.0f;
|
||||
float mMaxSample = 0.0f;
|
||||
float mAverageLevel = 0.0f;
|
||||
|
||||
// Enhanced beat detection components
|
||||
fl::unique_ptr<SpectralFluxDetector> mSpectralFluxDetector;
|
||||
fl::unique_ptr<PerceptualWeighting> mPerceptualWeighting;
|
||||
|
||||
// Enhanced beat detection state
|
||||
fl::array<float, 16> mPreviousMagnitudes;
|
||||
};
|
||||
|
||||
// Spectral flux-based onset detection for enhanced beat detection
|
||||
class SpectralFluxDetector {
|
||||
public:
|
||||
SpectralFluxDetector();
|
||||
~SpectralFluxDetector();
|
||||
|
||||
void reset();
|
||||
bool detectOnset(const float* currentBins, const float* previousBins);
|
||||
float calculateSpectralFlux(const float* currentBins, const float* previousBins);
|
||||
void setThreshold(float threshold);
|
||||
float getThreshold() const;
|
||||
|
||||
private:
|
||||
float mFluxThreshold;
|
||||
fl::array<float, 16> mPreviousMagnitudes;
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
fl::array<float, 32> mFluxHistory; // For advanced smoothing
|
||||
fl::size mHistoryIndex;
|
||||
float calculateAdaptiveThreshold();
|
||||
#endif
|
||||
};
|
||||
|
||||
// Multi-band beat detection for different frequency ranges
|
||||
struct BeatDetectors {
|
||||
BeatDetectors();
|
||||
~BeatDetectors();
|
||||
|
||||
void reset();
|
||||
void detectBeats(const float* frequencyBins, AudioData& audioData);
|
||||
void setThresholds(float bassThresh, float midThresh, float trebleThresh);
|
||||
|
||||
private:
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
SpectralFluxDetector bass; // 20-200 Hz (bins 0-1)
|
||||
SpectralFluxDetector mid; // 200-2000 Hz (bins 6-7)
|
||||
SpectralFluxDetector treble; // 2000-20000 Hz (bins 14-15)
|
||||
#else
|
||||
SpectralFluxDetector combined; // Single detector for memory-constrained
|
||||
#endif
|
||||
|
||||
// Energy tracking for band-specific thresholds
|
||||
float mBassEnergy;
|
||||
float mMidEnergy;
|
||||
float mTrebleEnergy;
|
||||
float mPreviousBassEnergy;
|
||||
float mPreviousMidEnergy;
|
||||
float mPreviousTrebleEnergy;
|
||||
};
|
||||
|
||||
// Perceptual audio weighting for psychoacoustic processing
|
||||
class PerceptualWeighting {
|
||||
public:
|
||||
PerceptualWeighting();
|
||||
~PerceptualWeighting();
|
||||
|
||||
void applyAWeighting(AudioData& data) const;
|
||||
void applyLoudnessCompensation(AudioData& data, float referenceLevel) const;
|
||||
|
||||
private:
|
||||
// A-weighting coefficients for 16-bin frequency analysis
|
||||
static constexpr float A_WEIGHTING_COEFFS[16] = {
|
||||
0.5f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.4f, 1.4f,
|
||||
1.3f, 1.2f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f
|
||||
};
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
fl::array<float, 16> mLoudnessHistory; // For dynamic compensation
|
||||
fl::size mHistoryIndex;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
8
libraries/FastLED/src/fl/avr_disallowed.h
Normal file
8
libraries/FastLED/src/fl/avr_disallowed.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __AVR__
|
||||
#define AVR_DISALLOWED \
|
||||
[[deprecated("This function or class is deprecated on AVR.")]]
|
||||
#else
|
||||
#define AVR_DISALLOWED
|
||||
#endif
|
||||
74
libraries/FastLED/src/fl/bit_cast.h
Normal file
74
libraries/FastLED/src/fl/bit_cast.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
// What is bit cast?
|
||||
// Bit cast is a safe version of reinterpret_cast that is robust against strict aliasing rules
|
||||
// that are used in aggressive compiler optimizations.
|
||||
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// bit_cast - Safe type-punning utility (C++20 std::bit_cast equivalent)
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// Helper trait for bitcast - check if a type can be bitcast (relax POD requirement)
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible {
|
||||
static constexpr bool value = fl::is_integral<T>::value ||
|
||||
fl::is_floating_point<T>::value ||
|
||||
fl::is_pod<T>::value;
|
||||
};
|
||||
|
||||
// Specializations for const types
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible<const T> {
|
||||
static constexpr bool value = is_bitcast_compatible<T>::value;
|
||||
};
|
||||
|
||||
// Specializations for pointer types
|
||||
template <typename T>
|
||||
struct is_bitcast_compatible<T*> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
// C++20-style bit_cast for safe type reinterpretation
|
||||
// Uses union for zero-cost type punning - compiler optimizes to direct assignment
|
||||
template <typename To, typename From>
|
||||
To bit_cast(const From& from) noexcept {
|
||||
static_assert(sizeof(To) == sizeof(From), "bit_cast: types must have the same size");
|
||||
static_assert(is_bitcast_compatible<To>::value, "bit_cast: destination type must be bitcast compatible");
|
||||
static_assert(is_bitcast_compatible<From>::value, "bit_cast: source type must be bitcast compatible");
|
||||
|
||||
union { // robust against strict aliasing rules
|
||||
From from_val;
|
||||
To to_val;
|
||||
} u;
|
||||
u.from_val = from;
|
||||
return u.to_val;
|
||||
}
|
||||
|
||||
// Overload for pointer types - converts storage pointer to typed pointer safely
|
||||
template <typename To>
|
||||
To* bit_cast_ptr(void* storage) noexcept {
|
||||
return bit_cast<To*>(storage);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
const To* bit_cast_ptr(const void* storage) noexcept {
|
||||
return bit_cast<const To*>(storage);
|
||||
}
|
||||
|
||||
// Additional utility for uptr conversions (common pattern in the codebase)
|
||||
template <typename T>
|
||||
uptr ptr_to_int(T* ptr) noexcept {
|
||||
return bit_cast<uptr>(ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* int_to_ptr(uptr value) noexcept {
|
||||
return bit_cast<T*>(value);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
28
libraries/FastLED/src/fl/bitset.cpp
Normal file
28
libraries/FastLED/src/fl/bitset.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "fl/bitset.h"
|
||||
|
||||
#include "fl/string.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace detail {
|
||||
void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst) {
|
||||
fl::string& result = *dst;
|
||||
constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16); // 16 bits per block
|
||||
|
||||
for (fl::u32 i = 0; i < bit_count; ++i) {
|
||||
const fl::u32 block_idx = i / bits_per_block;
|
||||
const fl::u32 bit_offset = i % bits_per_block;
|
||||
|
||||
// Extract the bit from the block
|
||||
bool bit_value = (bit_data[block_idx] >> bit_offset) & 1;
|
||||
result.append(bit_value ? "1" : "0");
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Implementation for bitset_dynamic::to_string
|
||||
void bitset_dynamic::to_string(string* dst) const {
|
||||
detail::to_string(_blocks, _size, dst);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
663
libraries/FastLED/src/fl/bitset.h
Normal file
663
libraries/FastLED/src/fl/bitset.h
Normal file
@@ -0,0 +1,663 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/bitset_dynamic.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(double-promotion)
|
||||
FL_DISABLE_WARNING(float-conversion)
|
||||
FL_DISABLE_WARNING(sign-conversion)
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <fl::u32 N> class BitsetInlined;
|
||||
|
||||
template <fl::u32 N> class BitsetFixed;
|
||||
|
||||
class string;
|
||||
template <fl::u32 N = 16>
|
||||
using bitset = BitsetInlined<N>; // inlined but can go bigger.
|
||||
|
||||
template <fl::u32 N>
|
||||
using bitset_fixed = BitsetFixed<N>; // fixed size, no dynamic allocation.
|
||||
|
||||
|
||||
// TODO: move this to fl/math.h
|
||||
template<typename IntType>
|
||||
inline fl::u8 popcount(IntType value) {
|
||||
return static_cast<fl::u8>(__builtin_popcount(value));
|
||||
}
|
||||
|
||||
template<typename IntType>
|
||||
inline fl::u8 countr_zero(IntType value) {
|
||||
return static_cast<fl::u8>(__builtin_ctz(value));
|
||||
}
|
||||
|
||||
/// A simple fixed-size Bitset implementation similar to std::Bitset.
|
||||
template <fl::u32 N> class BitsetFixed {
|
||||
private:
|
||||
static_assert(sizeof(fl::u16) == 2, "u16 should be 2 bytes");
|
||||
static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16);
|
||||
static constexpr fl::u32 block_count =
|
||||
(N + bits_per_block - 1) / bits_per_block;
|
||||
using block_type = fl::u16;
|
||||
|
||||
// Underlying blocks storing bits
|
||||
block_type _blocks[block_count];
|
||||
|
||||
public:
|
||||
struct Proxy {
|
||||
BitsetFixed &_bitset;
|
||||
fl::u32 _pos;
|
||||
|
||||
Proxy(BitsetFixed &bitset, fl::u32 pos) : _bitset(bitset), _pos(pos) {}
|
||||
|
||||
Proxy &operator=(bool value) {
|
||||
_bitset.set(_pos, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return _bitset.test(_pos); }
|
||||
};
|
||||
|
||||
Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); }
|
||||
|
||||
/// Constructs a BitsetFixed with all bits reset.
|
||||
constexpr BitsetFixed() noexcept : _blocks{} {}
|
||||
|
||||
void to_string(string* dst) const {
|
||||
detail::to_string(_blocks, N, dst);
|
||||
}
|
||||
|
||||
/// Resets all bits to zero.
|
||||
void reset() noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears the bit at position pos.
|
||||
BitsetFixed &set(fl::u32 pos, bool value = true) {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
if (value) {
|
||||
_blocks[idx] |= (block_type(1) << off);
|
||||
} else {
|
||||
_blocks[idx] &= ~(block_type(1) << off);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void assign(fl::size n, bool value) {
|
||||
if (n > N) {
|
||||
n = N;
|
||||
}
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
set(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the bit at position pos.
|
||||
BitsetFixed &reset(fl::u32 pos) { return set(pos, false); }
|
||||
|
||||
/// Flips (toggles) the bit at position pos.
|
||||
BitsetFixed &flip(fl::u32 pos) {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] ^= (block_type(1) << off);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Flips all bits.
|
||||
BitsetFixed &flip() noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] = ~_blocks[i];
|
||||
}
|
||||
// Mask out unused high bits in the last block
|
||||
if (N % bits_per_block != 0) {
|
||||
const fl::u32 extra = bits_per_block - (N % bits_per_block);
|
||||
_blocks[block_count - 1] &= (~block_type(0) >> extra);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Tests whether the bit at position pos is set.
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (pos < N) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
return (_blocks[idx] >> off) & 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns the value of the bit at position pos.
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Returns the number of set bits.
|
||||
fl::u32 count() const noexcept {
|
||||
fl::u32 cnt = 0;
|
||||
// Count bits in all complete blocks
|
||||
for (fl::u32 i = 0; i < block_count - 1; ++i) {
|
||||
cnt += fl::popcount(_blocks[i]);
|
||||
}
|
||||
|
||||
// For the last block, we need to be careful about counting only valid
|
||||
// bits
|
||||
if (block_count > 0) {
|
||||
block_type last_block = _blocks[block_count - 1];
|
||||
// If N is not a multiple of bits_per_block, mask out the unused
|
||||
// bits
|
||||
if (N % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = N % bits_per_block;
|
||||
// Create a mask with only the valid bits set to 1
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: ((block_type(1) << valid_bits) - 1);
|
||||
last_block &= mask;
|
||||
}
|
||||
cnt += fl::popcount(last_block);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/// Queries.
|
||||
bool any() const noexcept { return count() > 0; }
|
||||
bool none() const noexcept { return count() == 0; }
|
||||
bool all() const noexcept {
|
||||
if (N == 0)
|
||||
return true;
|
||||
|
||||
// Check all complete blocks
|
||||
for (fl::u32 i = 0; i < block_count - 1; ++i) {
|
||||
if (_blocks[i] != ~block_type(0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the last block
|
||||
if (block_count > 0) {
|
||||
block_type mask;
|
||||
if (N % bits_per_block != 0) {
|
||||
// Create a mask for the valid bits in the last block
|
||||
mask = (block_type(1) << (N % bits_per_block)) - 1;
|
||||
} else {
|
||||
mask = static_cast<block_type>(~block_type(0));
|
||||
}
|
||||
|
||||
if ((_blocks[block_count - 1] & mask) != mask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Bitwise AND
|
||||
BitsetFixed &operator&=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] &= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Bitwise OR
|
||||
BitsetFixed &operator|=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] |= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Bitwise XOR
|
||||
BitsetFixed &operator^=(const BitsetFixed &other) noexcept {
|
||||
for (fl::u32 i = 0; i < block_count; ++i) {
|
||||
_blocks[i] ^= other._blocks[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Size of the BitsetFixed (number of bits).
|
||||
constexpr fl::u32 size() const noexcept { return N; }
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
// If offset is beyond our size, no match possible
|
||||
if (offset >= N) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate which block to start from
|
||||
fl::u32 start_block = offset / bits_per_block;
|
||||
fl::u32 start_bit = offset % bits_per_block;
|
||||
|
||||
for (fl::u32 block_idx = start_block; block_idx < block_count; ++block_idx) {
|
||||
block_type current_block = _blocks[block_idx];
|
||||
|
||||
// For the last block, we need to mask out unused bits
|
||||
if (block_idx == block_count - 1 && N % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = N % bits_per_block;
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: ((block_type(1) << valid_bits) - 1);
|
||||
current_block &= mask;
|
||||
}
|
||||
|
||||
// If looking for false bits, invert the block
|
||||
if (!test_value) {
|
||||
current_block = ~current_block;
|
||||
}
|
||||
|
||||
// For the first block, mask out bits before the offset
|
||||
if (block_idx == start_block && start_bit > 0) {
|
||||
current_block &= ~((block_type(1) << start_bit) - 1);
|
||||
}
|
||||
|
||||
// If there are any matching bits in this block
|
||||
if (current_block != 0) {
|
||||
// Find the first set bit
|
||||
fl::u32 bit_pos = fl::countr_zero(current_block);
|
||||
fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos;
|
||||
|
||||
// Make sure we haven't gone past the end of the bitset
|
||||
if (absolute_pos < N) {
|
||||
return static_cast<fl::i32>(absolute_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // No matching bit found
|
||||
}
|
||||
|
||||
/// Finds the first run of consecutive bits that match the test value.
|
||||
/// Returns the index of the first bit in the run, or -1 if no run found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param min_length Minimum length of the run (default: 1)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_run(bool test_value, fl::u32 min_length, fl::u32 offset = 0) const noexcept {
|
||||
fl::u32 run_start = offset;
|
||||
fl::u32 run_length = 0;
|
||||
|
||||
for (fl::u32 i = offset; i < N && run_length < min_length; ++i) {
|
||||
bool current_bit = test(i);
|
||||
if (current_bit != test_value) {
|
||||
run_length = 0;
|
||||
if (i + 1 < N) {
|
||||
run_start = i + 1;
|
||||
}
|
||||
} else {
|
||||
++run_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (run_length >= min_length) {
|
||||
return static_cast<fl::i32>(run_start);
|
||||
}
|
||||
|
||||
return -1; // No run found
|
||||
}
|
||||
|
||||
/// Friend operators for convenience.
|
||||
friend BitsetFixed operator&(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs &= rhs;
|
||||
}
|
||||
friend BitsetFixed operator|(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
friend BitsetFixed operator^(BitsetFixed lhs,
|
||||
const BitsetFixed &rhs) noexcept {
|
||||
return lhs ^= rhs;
|
||||
}
|
||||
friend BitsetFixed operator~(BitsetFixed bs) noexcept { return bs.flip(); }
|
||||
};
|
||||
|
||||
/// A Bitset implementation with inline storage that can grow if needed.
|
||||
/// T is the storage type (u8, u16, u32, uint64_t)
|
||||
/// N is the initial number of bits to store inline
|
||||
template <fl::u32 N = 16> // Default size is 16 bits, or 2 bytes
|
||||
class BitsetInlined {
|
||||
private:
|
||||
// Either store a fixed Bitset<N> or a dynamic Bitset
|
||||
using fixed_bitset = BitsetFixed<N>;
|
||||
Variant<fixed_bitset, bitset_dynamic> _storage;
|
||||
|
||||
public:
|
||||
struct Proxy {
|
||||
BitsetInlined &_bitset;
|
||||
fl::u32 _pos;
|
||||
|
||||
Proxy(BitsetInlined &bitset, fl::u32 pos)
|
||||
: _bitset(bitset), _pos(pos) {}
|
||||
|
||||
Proxy &operator=(bool value) {
|
||||
_bitset.set(_pos, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return _bitset.test(_pos); }
|
||||
};
|
||||
|
||||
Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); }
|
||||
|
||||
/// Constructs a Bitset with all bits reset.
|
||||
BitsetInlined() : _storage(fixed_bitset()) {}
|
||||
BitsetInlined(fl::size size) : _storage(fixed_bitset()) {
|
||||
if (size > N) {
|
||||
_storage = bitset_dynamic(size);
|
||||
}
|
||||
}
|
||||
BitsetInlined(const BitsetInlined &other) : _storage(other._storage) {}
|
||||
BitsetInlined(BitsetInlined &&other) noexcept
|
||||
: _storage(fl::move(other._storage)) {}
|
||||
BitsetInlined &operator=(const BitsetInlined &other) {
|
||||
if (this != &other) {
|
||||
_storage = other._storage;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BitsetInlined &operator=(BitsetInlined &&other) noexcept {
|
||||
if (this != &other) {
|
||||
_storage = fl::move(other._storage);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Resets all bits to zero.
|
||||
void reset() noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->reset();
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void assign(fl::size n, bool value) {
|
||||
resize(n);
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->assign(n, value);
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->assign(n, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Resizes the Bitset if needed
|
||||
void resize(fl::u32 new_size) {
|
||||
if (new_size <= N) {
|
||||
// If we're already using the fixed Bitset, nothing to do
|
||||
if (_storage.template is<bitset_dynamic>()) {
|
||||
// Convert back to fixed Bitset
|
||||
fixed_bitset fixed;
|
||||
bitset_dynamic *dynamic =
|
||||
_storage.template ptr<bitset_dynamic>();
|
||||
|
||||
// Copy bits from dynamic to fixed
|
||||
for (fl::u32 i = 0; i < N && i < dynamic->size(); ++i) {
|
||||
if (dynamic->test(i)) {
|
||||
fixed.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
_storage = fixed;
|
||||
}
|
||||
} else {
|
||||
// Need to use dynamic Bitset
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
// Convert from fixed to dynamic
|
||||
bitset_dynamic dynamic(new_size);
|
||||
fixed_bitset *fixed = _storage.template ptr<fixed_bitset>();
|
||||
|
||||
// Copy bits from fixed to dynamic
|
||||
for (fl::u32 i = 0; i < N; ++i) {
|
||||
if (fixed->test(i)) {
|
||||
dynamic.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
_storage = dynamic;
|
||||
} else {
|
||||
// Already using dynamic, just resize
|
||||
_storage.template ptr<bitset_dynamic>()->resize(new_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears the bit at position pos.
|
||||
BitsetInlined &set(fl::u32 pos, bool value = true) {
|
||||
if (pos >= N && _storage.template is<fixed_bitset>()) {
|
||||
resize(pos + 1);
|
||||
}
|
||||
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
if (pos < N) {
|
||||
_storage.template ptr<fixed_bitset>()->set(pos, value);
|
||||
}
|
||||
} else {
|
||||
if (pos >= _storage.template ptr<bitset_dynamic>()->size()) {
|
||||
_storage.template ptr<bitset_dynamic>()->resize(pos + 1);
|
||||
}
|
||||
_storage.template ptr<bitset_dynamic>()->set(pos, value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Clears the bit at position pos.
|
||||
BitsetInlined &reset(fl::u32 pos) { return set(pos, false); }
|
||||
|
||||
/// Flips (toggles) the bit at position pos.
|
||||
BitsetInlined &flip(fl::u32 pos) {
|
||||
if (pos >= N && _storage.template is<fixed_bitset>()) {
|
||||
resize(pos + 1);
|
||||
}
|
||||
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
if (pos < N) {
|
||||
_storage.template ptr<fixed_bitset>()->flip(pos);
|
||||
}
|
||||
} else {
|
||||
if (pos >= _storage.template ptr<bitset_dynamic>()->size()) {
|
||||
_storage.template ptr<bitset_dynamic>()->resize(pos + 1);
|
||||
}
|
||||
_storage.template ptr<bitset_dynamic>()->flip(pos);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Flips all bits.
|
||||
BitsetInlined &flip() noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->flip();
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->flip();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Tests whether the bit at position pos is set.
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return pos < N ? _storage.template ptr<fixed_bitset>()->test(pos)
|
||||
: false;
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->test(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the bit at position pos.
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Returns the number of set bits.
|
||||
fl::u32 count() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->count();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->count();
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries.
|
||||
bool any() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->any();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->any();
|
||||
}
|
||||
}
|
||||
|
||||
bool none() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->none();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->none();
|
||||
}
|
||||
}
|
||||
|
||||
bool all() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->all();
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of the Bitset (number of bits).
|
||||
fl::u32 size() const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return N;
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->size();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert bitset to string representation
|
||||
void to_string(string* dst) const {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
_storage.template ptr<fixed_bitset>()->to_string(dst);
|
||||
} else {
|
||||
_storage.template ptr<bitset_dynamic>()->to_string(dst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
if (_storage.template is<fixed_bitset>()) {
|
||||
return _storage.template ptr<fixed_bitset>()->find_first(test_value, offset);
|
||||
} else {
|
||||
return _storage.template ptr<bitset_dynamic>()->find_first(test_value, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitwise operators
|
||||
friend BitsetInlined operator~(const BitsetInlined &bs) noexcept {
|
||||
BitsetInlined result = bs;
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator&(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() &=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 min_size =
|
||||
result.size() < rhs.size() ? result.size() : rhs.size();
|
||||
for (fl::u32 i = 0; i < min_size; ++i) {
|
||||
result.set(i, result.test(i) && rhs.test(i));
|
||||
}
|
||||
// Clear any bits beyond the size of rhs
|
||||
for (fl::u32 i = min_size; i < result.size(); ++i) {
|
||||
result.reset(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator|(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() |=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 max_size =
|
||||
result.size() > rhs.size() ? result.size() : rhs.size();
|
||||
|
||||
// Resize if needed
|
||||
if (result.size() < max_size) {
|
||||
result.resize(max_size);
|
||||
}
|
||||
|
||||
// Set bits from rhs
|
||||
for (fl::u32 i = 0; i < rhs.size(); ++i) {
|
||||
if (rhs.test(i)) {
|
||||
result.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
friend BitsetInlined operator^(const BitsetInlined &lhs,
|
||||
const BitsetInlined &rhs) noexcept {
|
||||
BitsetInlined result = lhs;
|
||||
|
||||
if (result._storage.template is<fixed_bitset>() &&
|
||||
rhs._storage.template is<fixed_bitset>()) {
|
||||
// Both are fixed, use the fixed implementation
|
||||
*result._storage.template ptr<fixed_bitset>() ^=
|
||||
*rhs._storage.template ptr<fixed_bitset>();
|
||||
} else {
|
||||
// At least one is dynamic, handle bit by bit
|
||||
fl::u32 max_size =
|
||||
result.size() > rhs.size() ? result.size() : rhs.size();
|
||||
|
||||
// Resize if needed
|
||||
if (result.size() < max_size) {
|
||||
result.resize(max_size);
|
||||
}
|
||||
|
||||
// XOR bits from rhs
|
||||
for (fl::u32 i = 0; i < rhs.size(); ++i) {
|
||||
result.set(i, result.test(i) != rhs.test(i));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
451
libraries/FastLED/src/fl/bitset_dynamic.h
Normal file
451
libraries/FastLED/src/fl/bitset_dynamic.h
Normal file
@@ -0,0 +1,451 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include <string.h> // for memcpy
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class string;
|
||||
|
||||
namespace detail {
|
||||
void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst);
|
||||
}
|
||||
|
||||
/// A dynamic bitset implementation that can be resized at runtime
|
||||
class bitset_dynamic {
|
||||
private:
|
||||
static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16);
|
||||
using block_type = fl::u16;
|
||||
|
||||
block_type *_blocks = nullptr;
|
||||
fl::u32 _block_count = 0;
|
||||
fl::u32 _size = 0;
|
||||
|
||||
// Helper to calculate block count from bit count
|
||||
static fl::u32 calc_block_count(fl::u32 bit_count) {
|
||||
return (bit_count + bits_per_block - 1) / bits_per_block;
|
||||
}
|
||||
|
||||
public:
|
||||
// Default constructor
|
||||
bitset_dynamic() = default;
|
||||
|
||||
// Constructor with initial size
|
||||
explicit bitset_dynamic(fl::u32 size) { resize(size); }
|
||||
|
||||
// Copy constructor
|
||||
bitset_dynamic(const bitset_dynamic &other) {
|
||||
if (other._size > 0) {
|
||||
resize(other._size);
|
||||
memcpy(_blocks, other._blocks, _block_count * sizeof(block_type));
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
bitset_dynamic(bitset_dynamic &&other) noexcept
|
||||
: _blocks(other._blocks), _block_count(other._block_count),
|
||||
_size(other._size) {
|
||||
other._blocks = nullptr;
|
||||
other._block_count = 0;
|
||||
other._size = 0;
|
||||
}
|
||||
|
||||
// Copy assignment
|
||||
bitset_dynamic &operator=(const bitset_dynamic &other) {
|
||||
if (this != &other) {
|
||||
if (other._size > 0) {
|
||||
resize(other._size);
|
||||
memcpy(_blocks, other._blocks,
|
||||
_block_count * sizeof(block_type));
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
bitset_dynamic &operator=(bitset_dynamic &&other) noexcept {
|
||||
if (this != &other) {
|
||||
delete[] _blocks;
|
||||
_blocks = other._blocks;
|
||||
_block_count = other._block_count;
|
||||
_size = other._size;
|
||||
other._blocks = nullptr;
|
||||
other._block_count = 0;
|
||||
other._size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~bitset_dynamic() { delete[] _blocks; }
|
||||
|
||||
// Assign n bits to the value specified
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void assign(fl::u32 n, bool value) {
|
||||
if (n > _size) {
|
||||
resize(n);
|
||||
}
|
||||
if (value) {
|
||||
// Set all bits to 1
|
||||
if (_blocks && _block_count > 0) {
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
_blocks[i] = static_cast<block_type>(~block_type(0));
|
||||
}
|
||||
// Clear any bits beyond the actual size
|
||||
if (_size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask = static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Set all bits to 0
|
||||
reset();
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Resize the bitset
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void resize(fl::u32 new_size) {
|
||||
if (new_size == _size)
|
||||
return;
|
||||
|
||||
fl::u32 new_block_count = (new_size + bits_per_block - 1) / bits_per_block;
|
||||
|
||||
if (new_block_count != _block_count) {
|
||||
block_type *new_blocks = new block_type[new_block_count];
|
||||
fl::memfill(new_blocks, 0, new_block_count * sizeof(block_type));
|
||||
|
||||
if (_blocks) {
|
||||
fl::u32 copy_blocks = MIN(_block_count, new_block_count);
|
||||
memcpy(new_blocks, _blocks, copy_blocks * sizeof(block_type));
|
||||
}
|
||||
|
||||
delete[] _blocks;
|
||||
_blocks = new_blocks;
|
||||
_block_count = new_block_count;
|
||||
}
|
||||
|
||||
_size = new_size;
|
||||
|
||||
// Clear any bits beyond the new size
|
||||
if (_blocks && _block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Clear the bitset (reset to empty)
|
||||
void clear() {
|
||||
delete[] _blocks;
|
||||
_blocks = nullptr;
|
||||
_block_count = 0;
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
// Reset all bits to 0
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void reset() noexcept {
|
||||
if (_blocks && _block_count > 0) {
|
||||
fl::memfill(_blocks, 0, _block_count * sizeof(block_type));
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Reset a specific bit to 0
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void reset(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] &= ~(static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Set a specific bit to 1
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void set(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] |= (static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Set a specific bit to a given value
|
||||
void set(fl::u32 pos, bool value) noexcept {
|
||||
if (value) {
|
||||
set(pos);
|
||||
} else {
|
||||
reset(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Flip a specific bit
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void flip(fl::u32 pos) noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
_blocks[idx] ^= (static_cast<block_type>(1) << off);
|
||||
}
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Flip all bits
|
||||
void flip() noexcept {
|
||||
if (!_blocks) return;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
_blocks[i] = ~_blocks[i];
|
||||
}
|
||||
|
||||
// Clear any bits beyond size
|
||||
if (_block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
_blocks[last_block_idx] &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Test if a bit is set
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
bool test(fl::u32 pos) const noexcept {
|
||||
if (_blocks && pos < _size) {
|
||||
const fl::u32 idx = pos / bits_per_block;
|
||||
const fl::u32 off = pos % bits_per_block;
|
||||
return (_blocks[idx] >> off) & 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Count the number of set bits
|
||||
fl::u32 count() const noexcept {
|
||||
if (!_blocks) return 0;
|
||||
|
||||
fl::u32 result = 0;
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
result += static_cast<fl::u32>(__builtin_popcount(_blocks[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if any bit is set
|
||||
bool any() const noexcept {
|
||||
if (!_blocks) return false;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
if (_blocks[i] != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if no bit is set
|
||||
bool none() const noexcept { return !any(); }
|
||||
|
||||
// Check if all bits are set
|
||||
bool all() const noexcept {
|
||||
if (_size == 0)
|
||||
return true;
|
||||
|
||||
if (!_blocks) return false;
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count - 1; ++i) {
|
||||
if (_blocks[i] != static_cast<block_type>(~block_type(0)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check last block with mask for valid bits
|
||||
if (_block_count > 0) {
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
return (_blocks[_block_count - 1] & mask) == mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the size of the bitset
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
fl::u32 size() const noexcept {
|
||||
// Note: _size is a member variable, not a pointer, so this should be safe
|
||||
// but we add this comment to clarify for static analysis
|
||||
return _size;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// Convert bitset to string representation
|
||||
void to_string(string* dst) const;
|
||||
|
||||
// Access operator
|
||||
bool operator[](fl::u32 pos) const noexcept { return test(pos); }
|
||||
|
||||
/// Finds the first bit that matches the test value.
|
||||
/// Returns the index of the first matching bit, or -1 if none found.
|
||||
/// @param test_value The value to search for (true or false)
|
||||
/// @param offset Starting position to search from (default: 0)
|
||||
fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept {
|
||||
// If offset is beyond our size, no match possible
|
||||
if (offset >= _size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate which block to start from
|
||||
fl::u32 start_block = offset / bits_per_block;
|
||||
fl::u32 start_bit = offset % bits_per_block;
|
||||
|
||||
for (fl::u32 block_idx = start_block; block_idx < _block_count; ++block_idx) {
|
||||
block_type current_block = _blocks[block_idx];
|
||||
|
||||
// For the last block, we need to mask out unused bits
|
||||
if (block_idx == _block_count - 1 && _size % bits_per_block != 0) {
|
||||
const fl::u32 valid_bits = _size % bits_per_block;
|
||||
block_type mask = (valid_bits == bits_per_block)
|
||||
? static_cast<block_type>(~block_type(0))
|
||||
: static_cast<block_type>(((block_type(1) << valid_bits) - 1));
|
||||
current_block &= mask;
|
||||
}
|
||||
|
||||
// If looking for false bits, invert the block
|
||||
if (!test_value) {
|
||||
current_block = ~current_block;
|
||||
}
|
||||
|
||||
// For the first block, mask out bits before the offset
|
||||
if (block_idx == start_block && start_bit > 0) {
|
||||
current_block &= ~static_cast<block_type>(((block_type(1) << start_bit) - 1));
|
||||
}
|
||||
|
||||
// If there are any matching bits in this block
|
||||
if (current_block != 0) {
|
||||
// Find the first set bit
|
||||
fl::u32 bit_pos = static_cast<fl::u32>(__builtin_ctz(current_block));
|
||||
fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos;
|
||||
|
||||
// Make sure we haven't gone past the end of the bitset
|
||||
if (absolute_pos < _size) {
|
||||
return static_cast<fl::i32>(absolute_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // No matching bit found
|
||||
}
|
||||
|
||||
// Bitwise AND operator
|
||||
bitset_dynamic operator&(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] & other._blocks[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise OR operator
|
||||
bitset_dynamic operator|(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] | other._blocks[i];
|
||||
}
|
||||
|
||||
// Copy remaining blocks from the larger bitset
|
||||
if (_block_count > min_blocks) {
|
||||
memcpy(result._blocks + min_blocks, _blocks + min_blocks,
|
||||
(_block_count - min_blocks) * sizeof(block_type));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise XOR operator
|
||||
bitset_dynamic operator^(const bitset_dynamic &other) const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !other._blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fl::u32 min_blocks = MIN(_block_count, other._block_count);
|
||||
|
||||
for (fl::u32 i = 0; i < min_blocks; ++i) {
|
||||
result._blocks[i] = _blocks[i] ^ other._blocks[i];
|
||||
}
|
||||
|
||||
// Copy remaining blocks from the larger bitset
|
||||
if (_block_count > min_blocks) {
|
||||
memcpy(result._blocks + min_blocks, _blocks + min_blocks,
|
||||
(_block_count - min_blocks) * sizeof(block_type));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bitwise NOT operator
|
||||
bitset_dynamic operator~() const {
|
||||
bitset_dynamic result(_size);
|
||||
|
||||
if (!_blocks || !result._blocks) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (fl::u32 i = 0; i < _block_count; ++i) {
|
||||
result._blocks[i] = ~_blocks[i];
|
||||
}
|
||||
|
||||
// Clear any bits beyond size
|
||||
if (_block_count > 0 && _size % bits_per_block != 0) {
|
||||
fl::u32 last_block_idx = (_size - 1) / bits_per_block;
|
||||
fl::u32 last_bit_pos = (_size - 1) % bits_per_block;
|
||||
block_type mask =
|
||||
static_cast<block_type>((static_cast<block_type>(1) << (last_bit_pos + 1)) - 1);
|
||||
result._blocks[last_block_idx] &= mask;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
134
libraries/FastLED/src/fl/blur.cpp
Normal file
134
libraries/FastLED/src/fl/blur.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/blur.h"
|
||||
#include "fl/colorutils_misc.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/deprecated.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "lib8tion/scale8.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Legacy XY function. This is a weak symbol that can be overridden by the user.
|
||||
fl::u16 XY(fl::u8 x, fl::u8 y) FL_LINK_WEAK;
|
||||
|
||||
FL_LINK_WEAK fl::u16 XY(fl::u8 x, fl::u8 y) {
|
||||
FASTLED_UNUSED(x);
|
||||
FASTLED_UNUSED(y);
|
||||
FASTLED_ASSERT(false, "the user didn't provide an XY function");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fl::u16 XY(fl::u8 x, fl::u8 y) {
|
||||
// return 0;
|
||||
// }
|
||||
// make this a weak symbol
|
||||
namespace {
|
||||
fl::u16 xy_legacy_wrapper(fl::u16 x, fl::u16 y, fl::u16 width,
|
||||
fl::u16 height) {
|
||||
FASTLED_UNUSED(width);
|
||||
FASTLED_UNUSED(height);
|
||||
return XY(x, y);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
|
||||
// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors.
|
||||
//
|
||||
// 0 = no spread at all
|
||||
// 64 = moderate spreading
|
||||
// 172 = maximum smooth, even spreading
|
||||
//
|
||||
// 173..255 = wider spreading, but increasing flicker
|
||||
//
|
||||
// Total light is NOT entirely conserved, so many repeated
|
||||
// calls to 'blur' will also result in the light fading,
|
||||
// eventually all the way to black; this is by design so that
|
||||
// it can be used to (slowly) clear the LEDs to black.
|
||||
void blur1d(CRGB *leds, fl::u16 numLeds, fract8 blur_amount) {
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u16 i = 0; i < numLeds; ++i) {
|
||||
CRGB cur = leds[i];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[i - 1] += part;
|
||||
leds[i] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xymap) {
|
||||
blurRows(leds, width, height, blur_amount, xymap);
|
||||
blurColumns(leds, width, height, blur_amount, xymap);
|
||||
}
|
||||
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount) {
|
||||
XYMap xy =
|
||||
XYMap::constructWithUserFunction(width, height, xy_legacy_wrapper);
|
||||
blur2d(leds, width, height, blur_amount, xy);
|
||||
}
|
||||
|
||||
void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xyMap) {
|
||||
|
||||
/* for( fl::u8 row = 0; row < height; row++) {
|
||||
CRGB* rowbase = leds + (row * width);
|
||||
blur1d( rowbase, width, blur_amount);
|
||||
}
|
||||
*/
|
||||
// blur rows same as columns, for irregular matrix
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
for (fl::u8 row = 0; row < height; row++) {
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u8 i = 0; i < width; i++) {
|
||||
CRGB cur = leds[xyMap.mapToIndex(i, row)];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[xyMap.mapToIndex(i - 1, row)] += part;
|
||||
leds[xyMap.mapToIndex(i, row)] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// blurColumns: perform a blur1d on each column of a rectangular matrix
|
||||
void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const XYMap &xyMap) {
|
||||
// blur columns
|
||||
fl::u8 keep = 255 - blur_amount;
|
||||
fl::u8 seep = blur_amount >> 1;
|
||||
for (fl::u8 col = 0; col < width; ++col) {
|
||||
CRGB carryover = CRGB::Black;
|
||||
for (fl::u8 i = 0; i < height; ++i) {
|
||||
CRGB cur = leds[xyMap.mapToIndex(col, i)];
|
||||
CRGB part = cur;
|
||||
part.nscale8(seep);
|
||||
cur.nscale8(keep);
|
||||
cur += carryover;
|
||||
if (i)
|
||||
leds[xyMap.mapToIndex(col, i - 1)] += part;
|
||||
leds[xyMap.mapToIndex(col, i)] = cur;
|
||||
carryover = part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
71
libraries/FastLED/src/fl/blur.h
Normal file
71
libraries/FastLED/src/fl/blur.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/int.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/deprecated.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @defgroup ColorBlurs Color Blurring Functions
|
||||
/// Functions for blurring colors
|
||||
/// @{
|
||||
|
||||
/// One-dimensional blur filter.
|
||||
/// Spreads light to 2 line neighbors.
|
||||
/// * 0 = no spread at all
|
||||
/// * 64 = moderate spreading
|
||||
/// * 172 = maximum smooth, even spreading
|
||||
/// * 173..255 = wider spreading, but increasing flicker
|
||||
///
|
||||
/// Total light is NOT entirely conserved, so many repeated
|
||||
/// calls to 'blur' will also result in the light fading,
|
||||
/// eventually all the way to black; this is by design so that
|
||||
/// it can be used to (slowly) clear the LEDs to black.
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param numLeds the number of LEDs to blur
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blur1d(CRGB *leds, u16 numLeds, fract8 blur_amount);
|
||||
|
||||
/// Two-dimensional blur filter.
|
||||
/// Spreads light to 8 XY neighbors.
|
||||
/// * 0 = no spread at all
|
||||
/// * 64 = moderate spreading
|
||||
/// * 172 = maximum smooth, even spreading
|
||||
/// * 173..255 = wider spreading, but increasing flicker
|
||||
///
|
||||
/// Total light is NOT entirely conserved, so many repeated
|
||||
/// calls to 'blur' will also result in the light fading,
|
||||
/// eventually all the way to black; this is by design so that
|
||||
/// it can be used to (slowly) clear the LEDs to black.
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param width the width of the matrix
|
||||
/// @param height the height of the matrix
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// Legacy version of blur2d, which does not require an XYMap but instead
|
||||
/// implicitly binds to XY() function. If you are hitting a linker error here,
|
||||
/// then use blur2d(..., const fl::XYMap& xymap) instead.
|
||||
void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount)
|
||||
FASTLED_DEPRECATED("Use blur2d(..., const fl::XYMap& xymap) instead");
|
||||
|
||||
/// Perform a blur1d() on every row of a rectangular matrix
|
||||
/// @see blur1d()
|
||||
/// @param leds a pointer to the LED array to blur
|
||||
/// @param width the width of the matrix
|
||||
/// @param height the height of the matrix
|
||||
/// @param blur_amount the amount of blur to apply
|
||||
void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// Perform a blur1d() on every column of a rectangular matrix
|
||||
/// @copydetails blurRows()
|
||||
void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount,
|
||||
const fl::XYMap &xymap);
|
||||
|
||||
/// @} ColorBlurs
|
||||
|
||||
} // namespace fl
|
||||
29
libraries/FastLED/src/fl/bytestream.h
Normal file
29
libraries/FastLED/src/fl/bytestream.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(ByteStream);
|
||||
|
||||
// An abstract class that represents a stream of bytes.
|
||||
class ByteStream {
|
||||
public:
|
||||
virtual ~ByteStream() {}
|
||||
virtual bool available(fl::size) const = 0;
|
||||
virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0;
|
||||
virtual const char *path() const = 0;
|
||||
virtual void close() {} // default is do nothing on close.
|
||||
// convenience functions
|
||||
virtual fl::size readCRGB(CRGB *dst, fl::size n) {
|
||||
return read((fl::u8 *)dst, n * 3) / 3;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
72
libraries/FastLED/src/fl/bytestreammemory.cpp
Normal file
72
libraries/FastLED/src/fl/bytestreammemory.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <string.h>
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/bytestreammemory.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
ByteStreamMemory::ByteStreamMemory(fl::u32 size_buffer)
|
||||
: mReadBuffer(size_buffer) {}
|
||||
|
||||
ByteStreamMemory::~ByteStreamMemory() = default;
|
||||
|
||||
bool ByteStreamMemory::available(fl::size n) const {
|
||||
return mReadBuffer.size() >= n;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::read(fl::u8 *dst, fl::size bytesToRead) {
|
||||
if (!available(bytesToRead) || dst == nullptr) {
|
||||
FASTLED_WARN("ByteStreamMemory::read: !available(bytesToRead): "
|
||||
<< bytesToRead
|
||||
<< " mReadBuffer.size(): " << mReadBuffer.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
fl::size actualBytesToRead = MIN(bytesToRead, mReadBuffer.size());
|
||||
fl::size bytesRead = 0;
|
||||
|
||||
while (bytesRead < actualBytesToRead) {
|
||||
fl::u8 &b = dst[bytesRead];
|
||||
mReadBuffer.pop_front(&b);
|
||||
bytesRead++;
|
||||
}
|
||||
|
||||
if (bytesRead == 0) {
|
||||
FASTLED_WARN("ByteStreamMemory::read: bytesRead == 0");
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::write(const fl::u8 *src, fl::size n) {
|
||||
if (src == nullptr || mReadBuffer.capacity() == 0) {
|
||||
FASTLED_WARN_IF(src == nullptr,
|
||||
"ByteStreamMemory::write: src == nullptr");
|
||||
FASTLED_WARN_IF(mReadBuffer.capacity() == 0,
|
||||
"ByteStreamMemory::write: mReadBuffer.capacity() == 0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fl::size written = 0;
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
if (mReadBuffer.full()) {
|
||||
FASTLED_WARN("ByteStreamMemory::write: mReadBuffer.full(): "
|
||||
<< mReadBuffer.size());
|
||||
break;
|
||||
}
|
||||
mReadBuffer.push_back(src[i]);
|
||||
++written;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
fl::size ByteStreamMemory::writeCRGB(const CRGB *src, fl::size n) {
|
||||
fl::size bytes_written = write(reinterpret_cast<const fl::u8 *>(src), n * 3);
|
||||
fl::size pixels_written = bytes_written / 3;
|
||||
return pixels_written;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/bytestreammemory.h
Normal file
31
libraries/FastLED/src/fl/bytestreammemory.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
|
||||
#include "fl/bytestream.h"
|
||||
#include "fl/circular_buffer.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(ByteStreamMemory);
|
||||
|
||||
class ByteStreamMemory : public ByteStream {
|
||||
public:
|
||||
ByteStreamMemory(fl::u32 size_buffer);
|
||||
~ByteStreamMemory() override;
|
||||
bool available(fl::size n) const override;
|
||||
fl::size read(fl::u8 *dst, fl::size bytesToRead) override;
|
||||
void clear() { mReadBuffer.clear(); }
|
||||
const char *path() const override { return "ByteStreamMemory"; }
|
||||
fl::size write(const fl::u8 *src, fl::size n);
|
||||
fl::size writeCRGB(const CRGB *src, fl::size n);
|
||||
|
||||
private:
|
||||
CircularBuffer<fl::u8> mReadBuffer;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
143
libraries/FastLED/src/fl/circular_buffer.h
Normal file
143
libraries/FastLED/src/fl/circular_buffer.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/scoped_array.h"
|
||||
#include "fl/stdint.h" // For standard integer types
|
||||
|
||||
namespace fl {
|
||||
|
||||
// TODO:
|
||||
// ERROR bit to indicate over flow.
|
||||
|
||||
// Static version with compile-time capacity
|
||||
template <typename T, fl::size N>
|
||||
class StaticCircularBuffer {
|
||||
public:
|
||||
StaticCircularBuffer() : mHead(0), mTail(0) {}
|
||||
|
||||
void push(const T &value) {
|
||||
if (full()) {
|
||||
mTail = (mTail + 1) % (N + 1); // Overwrite the oldest element
|
||||
}
|
||||
mBuffer[mHead] = value;
|
||||
mHead = (mHead + 1) % (N + 1);
|
||||
}
|
||||
|
||||
bool pop(T &value) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
value = mBuffer[mTail];
|
||||
mTail = (mTail + 1) % (N + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
fl::size size() const { return (mHead + N + 1 - mTail) % (N + 1); }
|
||||
constexpr fl::size capacity() const { return N; }
|
||||
bool empty() const { return mHead == mTail; }
|
||||
bool full() const { return ((mHead + 1) % (N + 1)) == mTail; }
|
||||
void clear() { mHead = mTail = 0; }
|
||||
|
||||
private:
|
||||
T mBuffer[N + 1]; // Extra space for distinguishing full/empty
|
||||
fl::size mHead;
|
||||
fl::size mTail;
|
||||
};
|
||||
|
||||
// Dynamic version with runtime capacity (existing implementation)
|
||||
template <typename T> class DynamicCircularBuffer {
|
||||
public:
|
||||
DynamicCircularBuffer(fl::size capacity)
|
||||
: mCapacity(capacity + 1), mHead(0),
|
||||
mTail(0) { // Extra space for distinguishing full/empty
|
||||
mBuffer.reset(new T[mCapacity]);
|
||||
}
|
||||
|
||||
DynamicCircularBuffer(const DynamicCircularBuffer &) = delete;
|
||||
DynamicCircularBuffer &operator=(const DynamicCircularBuffer &) = delete;
|
||||
|
||||
bool push_back(const T &value) {
|
||||
if (full()) {
|
||||
mTail = increment(mTail); // Overwrite the oldest element
|
||||
}
|
||||
mBuffer[mHead] = value;
|
||||
mHead = increment(mHead);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop_front(T *dst = nullptr) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
if (dst) {
|
||||
*dst = mBuffer[mTail];
|
||||
}
|
||||
mTail = increment(mTail);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push_front(const T &value) {
|
||||
if (full()) {
|
||||
mHead = decrement(mHead); // Overwrite the oldest element
|
||||
}
|
||||
mTail = decrement(mTail);
|
||||
mBuffer[mTail] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop_back(T *dst = nullptr) {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
mHead = decrement(mHead);
|
||||
if (dst) {
|
||||
*dst = mBuffer[mHead];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
T &front() { return mBuffer[mTail]; }
|
||||
|
||||
const T &front() const { return mBuffer[mTail]; }
|
||||
|
||||
T &back() { return mBuffer[(mHead + mCapacity - 1) % mCapacity]; }
|
||||
|
||||
const T &back() const {
|
||||
return mBuffer[(mHead + mCapacity - 1) % mCapacity];
|
||||
}
|
||||
|
||||
T &operator[](fl::size index) { return mBuffer[(mTail + index) % mCapacity]; }
|
||||
|
||||
const T &operator[](fl::size index) const {
|
||||
return mBuffer[(mTail + index) % mCapacity];
|
||||
}
|
||||
|
||||
fl::size size() const { return (mHead + mCapacity - mTail) % mCapacity; }
|
||||
|
||||
fl::size capacity() const { return mCapacity - 1; }
|
||||
|
||||
bool empty() const { return mHead == mTail; }
|
||||
|
||||
bool full() const { return increment(mHead) == mTail; }
|
||||
|
||||
void clear() { mHead = mTail = 0; }
|
||||
|
||||
private:
|
||||
fl::size increment(fl::size index) const { return (index + 1) % mCapacity; }
|
||||
|
||||
fl::size decrement(fl::size index) const {
|
||||
return (index + mCapacity - 1) % mCapacity;
|
||||
}
|
||||
|
||||
fl::scoped_array<T> mBuffer;
|
||||
fl::size mCapacity;
|
||||
fl::size mHead;
|
||||
fl::size mTail;
|
||||
};
|
||||
|
||||
// For backward compatibility, keep the old name for the dynamic version
|
||||
template <typename T>
|
||||
using CircularBuffer = DynamicCircularBuffer<T>;
|
||||
|
||||
} // namespace fl
|
||||
20
libraries/FastLED/src/fl/clamp.h
Normal file
20
libraries/FastLED/src/fl/clamp.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/force_inline.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T> FASTLED_FORCE_INLINE T clamp(T value, T min, T max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
39
libraries/FastLED/src/fl/clear.h
Normal file
39
libraries/FastLED/src/fl/clear.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/leds.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template<typename T>
|
||||
class Grid;
|
||||
|
||||
// Memory safe clear function for CRGB arrays.
|
||||
template <int N> inline void clear(CRGB (&arr)[N]) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
arr[i] = CRGB::Black;
|
||||
}
|
||||
}
|
||||
|
||||
inline void clear(Leds &leds) { leds.fill(CRGB::Black); }
|
||||
|
||||
template<fl::size W, fl::size H>
|
||||
inline void clear(LedsXY<W, H> &leds) {
|
||||
leds.fill(CRGB::Black);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void clear(Grid<T> &grid) {
|
||||
grid.clear();
|
||||
}
|
||||
|
||||
// Default, when you don't know what do then call clear.
|
||||
template<typename Container>
|
||||
inline void clear(Container &container) {
|
||||
container.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
1109
libraries/FastLED/src/fl/colorutils.cpp
Normal file
1109
libraries/FastLED/src/fl/colorutils.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1807
libraries/FastLED/src/fl/colorutils.h
Normal file
1807
libraries/FastLED/src/fl/colorutils.h
Normal file
File diff suppressed because it is too large
Load Diff
41
libraries/FastLED/src/fl/colorutils_misc.h
Normal file
41
libraries/FastLED/src/fl/colorutils_misc.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
// TODO: Figure out how to namespace these.
|
||||
typedef fl::u32 TProgmemRGBPalette16[16]; ///< CRGBPalette16 entries stored in
|
||||
///< PROGMEM memory
|
||||
typedef fl::u32 TProgmemHSVPalette16[16]; ///< CHSVPalette16 entries stored in
|
||||
///< PROGMEM memory
|
||||
/// Alias for TProgmemRGBPalette16
|
||||
#define TProgmemPalette16 TProgmemRGBPalette16
|
||||
typedef fl::u32 TProgmemRGBPalette32[32]; ///< CRGBPalette32 entries stored in
|
||||
///< PROGMEM memory
|
||||
typedef fl::u32 TProgmemHSVPalette32[32]; ///< CHSVPalette32 entries stored in
|
||||
///< PROGMEM memory
|
||||
/// Alias for TProgmemRGBPalette32
|
||||
#define TProgmemPalette32 TProgmemRGBPalette32
|
||||
|
||||
/// Byte of an RGB gradient, stored in PROGMEM memory
|
||||
typedef const fl::u8 TProgmemRGBGradientPalette_byte;
|
||||
/// Pointer to bytes of an RGB gradient, stored in PROGMEM memory
|
||||
/// @see DEFINE_GRADIENT_PALETTE
|
||||
/// @see DECLARE_GRADIENT_PALETTE
|
||||
typedef const TProgmemRGBGradientPalette_byte *TProgmemRGBGradientPalette_bytes;
|
||||
/// Alias of ::TProgmemRGBGradientPalette_bytes
|
||||
typedef TProgmemRGBGradientPalette_bytes TProgmemRGBGradientPaletteRef;
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// Hue direction for calculating fill gradients.
|
||||
/// Since "hue" is a value around a color wheel, there are always two directions
|
||||
/// to sweep from one hue to another.
|
||||
typedef enum {
|
||||
FORWARD_HUES, ///< Hue always goes clockwise around the color wheel
|
||||
BACKWARD_HUES, ///< Hue always goes counter-clockwise around the color wheel
|
||||
SHORTEST_HUES, ///< Hue goes whichever way is shortest
|
||||
LONGEST_HUES ///< Hue goes whichever way is longest
|
||||
} TGradientDirectionCode;
|
||||
|
||||
} // namespace fl
|
||||
9
libraries/FastLED/src/fl/comparators.h
Normal file
9
libraries/FastLED/src/fl/comparators.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/utility.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// DefaultLess is now an alias for less<T>, defined in utility.h
|
||||
|
||||
} // namespace fl
|
||||
146
libraries/FastLED/src/fl/compiler_control.h
Normal file
146
libraries/FastLED/src/fl/compiler_control.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
// Stringify helper for pragma arguments
|
||||
#define FL_STRINGIFY2(x) #x
|
||||
#define FL_STRINGIFY(x) FL_STRINGIFY2(x)
|
||||
|
||||
// BEGIN BASE MACROS
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_PUSH _Pragma("clang diagnostic push")
|
||||
#define FL_DISABLE_WARNING_POP _Pragma("clang diagnostic pop")
|
||||
// Usage: FL_DISABLE_WARNING(float-equal)
|
||||
#define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(clang diagnostic ignored "-W" #warning))
|
||||
|
||||
#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406
|
||||
#define FL_DISABLE_WARNING_PUSH _Pragma("GCC diagnostic push")
|
||||
#define FL_DISABLE_WARNING_POP _Pragma("GCC diagnostic pop")
|
||||
// Usage: FL_DISABLE_WARNING(float-equal)
|
||||
#define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(GCC diagnostic ignored "-W" #warning))
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_PUSH
|
||||
#define FL_DISABLE_WARNING_POP
|
||||
#define FL_DISABLE_WARNING(warning)
|
||||
#endif
|
||||
// END BASE MACROS
|
||||
|
||||
// WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL.
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED \
|
||||
FL_DISABLE_WARNING(self-assign-overloaded)
|
||||
// Clang doesn't have format-truncation warning, use no-op
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE FL_DISABLE_WARNING(null-dereference)
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION FL_DISABLE_WARNING(implicit-int-conversion)
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION FL_DISABLE_WARNING(float-conversion)
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION FL_DISABLE_WARNING(sign-conversion)
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32 FL_DISABLE_WARNING(shorten-64-to-32)
|
||||
#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406
|
||||
// GCC doesn't have global-constructors warning, use no-op
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
// GCC doesn't have self-assign-overloaded warning, use no-op
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED
|
||||
// GCC has format-truncation warning
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION \
|
||||
FL_DISABLE_WARNING(format-truncation)
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER \
|
||||
FL_DISABLE_WARNING(unused-parameter)
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE \
|
||||
FL_DISABLE_WARNING(return-type)
|
||||
|
||||
// implicit-fallthrough warning requires GCC >= 7.0
|
||||
#if (__GNUC__*100 + __GNUC_MINOR__) >= 700
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH FL_DISABLE_WARNING(implicit-fallthrough)
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH
|
||||
#endif
|
||||
// GCC doesn't support these conversion warnings on older versions
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
// GCC doesn't have shorten-64-to-32 warning, use no-op
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
#define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED
|
||||
#define FL_DISABLE_FORMAT_TRUNCATION
|
||||
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
#define FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
#define FL_DISABLE_WARNING_RETURN_TYPE
|
||||
#define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
|
||||
#endif
|
||||
|
||||
// END WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL.
|
||||
|
||||
// Fast math optimization controls with additional aggressive flags
|
||||
#if defined(__clang__)
|
||||
#define FL_FAST_MATH_BEGIN \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("STDC FP_CONTRACT ON")
|
||||
|
||||
#define FL_FAST_MATH_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_FAST_MATH_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"fast-math\")") \
|
||||
_Pragma("GCC optimize (\"tree-vectorize\")") \
|
||||
_Pragma("GCC optimize (\"unroll-loops\")")
|
||||
|
||||
#define FL_FAST_MATH_END _Pragma("GCC pop_options")
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
#define FL_FAST_MATH_BEGIN __pragma(float_control(precise, off))
|
||||
#define FL_FAST_MATH_END __pragma(float_control(precise, on))
|
||||
#else
|
||||
#define FL_FAST_MATH_BEGIN /* nothing */
|
||||
#define FL_FAST_MATH_END /* nothing */
|
||||
#endif
|
||||
|
||||
// Optimization Level O3
|
||||
#if defined(__clang__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN \
|
||||
_Pragma("clang diagnostic push")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"O3\")")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("GCC pop_options")
|
||||
#else
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN /* nothing */
|
||||
#define FL_OPTIMIZATION_LEVEL_O3_END /* nothing */
|
||||
#endif
|
||||
|
||||
// Optimization Level O0 (Debug/No optimization)
|
||||
#if defined(__clang__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN \
|
||||
_Pragma("clang diagnostic push")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("clang diagnostic pop")
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN \
|
||||
_Pragma("GCC push_options") \
|
||||
_Pragma("GCC optimize (\"O0\")")
|
||||
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("GCC pop_options")
|
||||
#else
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN /* nothing */
|
||||
#define FL_OPTIMIZATION_LEVEL_O0_END /* nothing */
|
||||
#endif
|
||||
|
||||
#ifndef FL_LINK_WEAK
|
||||
#define FL_LINK_WEAK __attribute__((weak))
|
||||
#endif
|
||||
15
libraries/FastLED/src/fl/convert.h
Normal file
15
libraries/FastLED/src/fl/convert.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/int.h"
|
||||
// Conversion from FastLED timings to the type found in datasheets.
|
||||
inline void convert_fastled_timings_to_timedeltas(fl::u16 T1, fl::u16 T2,
|
||||
fl::u16 T3, fl::u16 *T0H,
|
||||
fl::u16 *T0L, fl::u16 *T1H,
|
||||
fl::u16 *T1L) {
|
||||
*T0H = T1;
|
||||
*T0L = T2 + T3;
|
||||
*T1H = T1 + T2;
|
||||
*T1L = T3;
|
||||
}
|
||||
502
libraries/FastLED/src/fl/corkscrew.cpp
Normal file
502
libraries/FastLED/src/fl/corkscrew.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/splat.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/map_range.h"
|
||||
#include "fl/leds.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
|
||||
// New helper function to calculate individual LED position
|
||||
vec2f calculateLedPositionExtended(fl::u16 ledIndex, fl::u16 numLeds, float totalTurns, const Gap& gapParams, fl::u16 width, fl::u16 height) {
|
||||
FL_UNUSED(height);
|
||||
FL_UNUSED(totalTurns);
|
||||
|
||||
// Check if gap feature is active AND will actually be triggered
|
||||
bool gapActive = (gapParams.num_leds > 0 && gapParams.gap > 0.0f && numLeds > static_cast<fl::u16>(gapParams.num_leds));
|
||||
|
||||
if (!gapActive) {
|
||||
// Original behavior when no gap or gap never triggers
|
||||
const float ledProgress = static_cast<float>(ledIndex) / static_cast<float>(numLeds - 1);
|
||||
const fl::u16 row = ledIndex / width;
|
||||
const fl::u16 remainder = ledIndex % width;
|
||||
const float alpha = static_cast<float>(remainder) / static_cast<float>(width);
|
||||
const float width_pos = ledProgress * numLeds;
|
||||
const float height_pos = static_cast<float>(row) + alpha;
|
||||
return vec2f(width_pos, height_pos);
|
||||
}
|
||||
|
||||
// Simplified gap calculation based on user expectation
|
||||
// User wants: LED0=0, LED1=3, LED2=6(wraps to 0) with width=3
|
||||
// This suggests they want regular spacing of width units per LED
|
||||
|
||||
// Simple spacing: each LED is separated by exactly width units
|
||||
float width_pos = static_cast<float>(ledIndex) * static_cast<float>(width);
|
||||
|
||||
// For height, divide by width to get turn progress
|
||||
float height_pos = width_pos / static_cast<float>(width);
|
||||
|
||||
return vec2f(width_pos, height_pos);
|
||||
}
|
||||
|
||||
void calculateDimensions(float totalTurns, fl::u16 numLeds, const Gap& gapParams, fl::u16 *width, fl::u16 *height) {
|
||||
FL_UNUSED(gapParams);
|
||||
|
||||
// Calculate optimal width and height
|
||||
float ledsPerTurn = static_cast<float>(numLeds) / totalTurns;
|
||||
fl::u16 calc_width = static_cast<fl::u16>(fl::ceil(ledsPerTurn));
|
||||
|
||||
fl::u16 height_from_turns = static_cast<fl::u16>(fl::ceil(totalTurns));
|
||||
fl::u16 calc_height;
|
||||
|
||||
// If the grid would have more pixels than LEDs, adjust height to better match
|
||||
if (calc_width * height_from_turns > numLeds) {
|
||||
// Calculate height that better matches LED count
|
||||
calc_height = static_cast<fl::u16>(fl::ceil(static_cast<float>(numLeds) / static_cast<float>(calc_width)));
|
||||
} else {
|
||||
calc_height = height_from_turns;
|
||||
}
|
||||
|
||||
*width = calc_width;
|
||||
*height = calc_height;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// New primary constructor
|
||||
Corkscrew::Corkscrew(float totalTurns, fl::u16 numLeds, bool invert, const Gap& gapParams)
|
||||
: mTotalTurns(totalTurns), mNumLeds(numLeds), mGapParams(gapParams), mInvert(invert) {
|
||||
fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
|
||||
mOwnsPixels = false;
|
||||
}
|
||||
|
||||
// Constructor with external pixel buffer
|
||||
Corkscrew::Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert, const Gap& gapParams)
|
||||
: mTotalTurns(totalTurns), mNumLeds(static_cast<fl::u16>(dstPixels.size())),
|
||||
mGapParams(gapParams), mInvert(invert) {
|
||||
fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
|
||||
mPixelStorage = dstPixels;
|
||||
mOwnsPixels = false; // External span
|
||||
}
|
||||
|
||||
|
||||
|
||||
vec2f Corkscrew::at_no_wrap(fl::u16 i) const {
|
||||
if (i >= mNumLeds) {
|
||||
// Handle out-of-bounds access, possibly by returning a default value
|
||||
return vec2f(0, 0);
|
||||
}
|
||||
|
||||
// Compute position on-the-fly
|
||||
vec2f position = calculateLedPositionExtended(i, mNumLeds, mTotalTurns,
|
||||
mGapParams, mWidth, mHeight);
|
||||
|
||||
// // Apply inversion if requested
|
||||
// if (mInvert) {
|
||||
// fl::u16 invertedIndex = mNumLeds - 1 - i;
|
||||
// position = calculateLedPositionExtended(invertedIndex, mNumLeds, mTotalTurns,
|
||||
// mGapParams, mState.width, mState.height);
|
||||
// }
|
||||
|
||||
// now wrap the x-position
|
||||
//position.x = fmodf(position.x, static_cast<float>(mState.width));
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
vec2f Corkscrew::at_exact(fl::u16 i) const {
|
||||
// Get the unwrapped position
|
||||
vec2f position = at_no_wrap(i);
|
||||
|
||||
// Apply cylindrical wrapping to the x-position (like at_wrap does)
|
||||
position.x = fmodf(position.x, static_cast<float>(mWidth));
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const {
|
||||
if (i >= mNumLeds) {
|
||||
// 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: " << mNumLeds);
|
||||
return Tile2x2_u8();
|
||||
}
|
||||
|
||||
// Use the splat function to convert the vec2f to a Tile2x2_u8
|
||||
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
|
||||
vec2f position = at_no_wrap(static_cast<fl::u16>(i_floor));
|
||||
return splat(position);
|
||||
} else {
|
||||
// Interpolate between the two points and return the splat of the result
|
||||
vec2f pos1 = at_no_wrap(static_cast<fl::u16>(i_floor));
|
||||
vec2f pos2 = at_no_wrap(static_cast<fl::u16>(i_ceil));
|
||||
float t = i - i_floor;
|
||||
vec2f interpolated_pos = map_range(t, 0.0f, 1.0f, pos1, pos2);
|
||||
return splat(interpolated_pos);
|
||||
}
|
||||
}
|
||||
|
||||
fl::size Corkscrew::size() const { return mNumLeds; }
|
||||
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const {
|
||||
if (mCachingEnabled) {
|
||||
// Use cache if enabled
|
||||
initializeCache();
|
||||
|
||||
// Convert float index to integer for cache lookup
|
||||
fl::size cache_index = static_cast<fl::size>(i);
|
||||
if (cache_index < mTileCache.size()) {
|
||||
return mTileCache[cache_index];
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to dynamic calculation if cache disabled or index out of bounds
|
||||
return calculateTileAtWrap(i);
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::calculateTileAtWrap(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);
|
||||
Tile2x2_u8_wrap::Entry data[2][2];
|
||||
vec2<u16> origin = tile.origin();
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
// For each pixel in the tile, map it to the cylinder so that each subcomponent
|
||||
// is mapped to the correct position on the cylinder.
|
||||
vec2<u16> pos = origin + vec2<u16>(x, y);
|
||||
// now wrap the x-position
|
||||
pos.x = fmodf(pos.x, static_cast<float>(mWidth));
|
||||
data[x][y] = {pos, tile.at(x, y)};
|
||||
}
|
||||
}
|
||||
return Tile2x2_u8_wrap(data);
|
||||
}
|
||||
|
||||
void Corkscrew::setCachingEnabled(bool enabled) {
|
||||
if (!enabled && mCachingEnabled) {
|
||||
// Caching was enabled, now disabling - clear the cache
|
||||
mTileCache.clear();
|
||||
mCacheInitialized = false;
|
||||
}
|
||||
mCachingEnabled = enabled;
|
||||
}
|
||||
|
||||
void Corkscrew::initializeCache() const {
|
||||
if (!mCacheInitialized && mCachingEnabled) {
|
||||
// Initialize cache with tiles for each LED position
|
||||
mTileCache.resize(mNumLeds);
|
||||
|
||||
// Populate cache lazily
|
||||
for (fl::size i = 0; i < mNumLeds; ++i) {
|
||||
mTileCache[i] = calculateTileAtWrap(static_cast<float>(i));
|
||||
}
|
||||
|
||||
mCacheInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
CRGB* Corkscrew::rawData() {
|
||||
// Use variant storage if available, otherwise fall back to input surface
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>().data();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
return mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>().data();
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input surface data
|
||||
auto surface = getOrCreateInputSurface();
|
||||
return surface->data();
|
||||
}
|
||||
|
||||
fl::span<CRGB> Corkscrew::data() {
|
||||
// Use variant storage if available, otherwise fall back to input surface
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
auto& vec = mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>();
|
||||
return fl::span<CRGB>(vec.data(), vec.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input surface data as span
|
||||
auto surface = getOrCreateInputSurface();
|
||||
return fl::span<CRGB>(surface->data(), surface->size());
|
||||
}
|
||||
|
||||
|
||||
void Corkscrew::readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling) {
|
||||
|
||||
if (use_multi_sampling) {
|
||||
readFromMulti(source_grid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the input surface
|
||||
auto target_surface = getOrCreateInputSurface();
|
||||
|
||||
// Clear surface first
|
||||
target_surface->clear();
|
||||
|
||||
// Iterate through each LED in the corkscrew
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the rectangular coordinates for this corkscrew LED
|
||||
vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
|
||||
|
||||
// Convert to integer coordinates for indexing
|
||||
vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
|
||||
static_cast<fl::i16>(rect_pos.y + 0.5f));
|
||||
|
||||
// Clamp coordinates to grid bounds
|
||||
coord.x = MAX(0, MIN(coord.x, static_cast<fl::i16>(source_grid.width()) - 1));
|
||||
coord.y = MAX(0, MIN(coord.y, static_cast<fl::i16>(source_grid.height()) - 1));
|
||||
|
||||
// Sample from the source fl::Grid using its at() method
|
||||
CRGB sampled_color = source_grid.at(coord.x, coord.y);
|
||||
|
||||
// Store the sampled color directly in the target surface
|
||||
if (led_idx < target_surface->size()) {
|
||||
target_surface->data()[led_idx] = sampled_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::clear() {
|
||||
// Clear input surface if it exists
|
||||
if (mInputSurface) {
|
||||
mInputSurface->clear();
|
||||
mInputSurface.reset(); // Free the shared_ptr memory
|
||||
}
|
||||
|
||||
// Clear pixel storage if we own it (vector variant)
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
auto& vec = mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>();
|
||||
vec.clear();
|
||||
// Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
|
||||
}
|
||||
// Note: Don't clear external spans as we don't own that memory
|
||||
}
|
||||
|
||||
// Clear tile cache
|
||||
mTileCache.clear();
|
||||
// Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
|
||||
mCacheInitialized = false;
|
||||
}
|
||||
|
||||
void Corkscrew::fillInputSurface(const CRGB& color) {
|
||||
auto target_surface = getOrCreateInputSurface();
|
||||
for (fl::size i = 0; i < target_surface->size(); ++i) {
|
||||
target_surface->data()[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::draw(bool use_multi_sampling) {
|
||||
// The draw method should map from the rectangular surface to the LED pixel data
|
||||
// This is the reverse of readFrom - we read from our surface and populate LED data
|
||||
auto source_surface = getOrCreateInputSurface();
|
||||
|
||||
// Make sure we have pixel storage
|
||||
if (mPixelStorage.empty()) {
|
||||
// If no pixel storage is configured, there's nothing to draw to
|
||||
return;
|
||||
}
|
||||
|
||||
CRGB* led_data = rawData();
|
||||
if (!led_data) return;
|
||||
|
||||
if (use_multi_sampling) {
|
||||
// Use multi-sampling to get better accuracy
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the wrapped tile for this LED position
|
||||
Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
|
||||
|
||||
// Accumulate color from the 4 sample points with their weights
|
||||
fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
|
||||
fl::u32 total_weight = 0;
|
||||
|
||||
// Sample from each of the 4 corners of the tile
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
const auto& entry = tile.at(x, y);
|
||||
vec2<u16> pos = entry.first;
|
||||
fl::u8 weight = entry.second;
|
||||
|
||||
// Bounds check for the source surface
|
||||
if (pos.x < source_surface->width() && pos.y < source_surface->height()) {
|
||||
// Sample from the source surface
|
||||
CRGB sample_color = source_surface->at(pos.x, pos.y);
|
||||
|
||||
// Accumulate weighted color components
|
||||
r_accum += static_cast<fl::u32>(sample_color.r) * weight;
|
||||
g_accum += static_cast<fl::u32>(sample_color.g) * weight;
|
||||
b_accum += static_cast<fl::u32>(sample_color.b) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final color by dividing by total weight
|
||||
CRGB final_color = CRGB::Black;
|
||||
if (total_weight > 0) {
|
||||
final_color.r = static_cast<fl::u8>(r_accum / total_weight);
|
||||
final_color.g = static_cast<fl::u8>(g_accum / total_weight);
|
||||
final_color.b = static_cast<fl::u8>(b_accum / total_weight);
|
||||
}
|
||||
|
||||
// Store the result in the LED data
|
||||
led_data[led_idx] = final_color;
|
||||
}
|
||||
} else {
|
||||
// Simple non-multi-sampling version
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the rectangular coordinates for this corkscrew LED
|
||||
vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
|
||||
|
||||
// Convert to integer coordinates for indexing
|
||||
vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
|
||||
static_cast<fl::i16>(rect_pos.y + 0.5f));
|
||||
|
||||
// Clamp coordinates to surface bounds
|
||||
coord.x = MAX(0, MIN(coord.x, static_cast<fl::i16>(source_surface->width()) - 1));
|
||||
coord.y = MAX(0, MIN(coord.y, static_cast<fl::i16>(source_surface->height()) - 1));
|
||||
|
||||
// Sample from the source surface
|
||||
CRGB sampled_color = source_surface->at(coord.x, coord.y);
|
||||
|
||||
// Store the sampled color in the LED data
|
||||
led_data[led_idx] = sampled_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Corkscrew::readFromMulti(const fl::Grid<CRGB>& source_grid) const {
|
||||
// Get the target surface and clear it
|
||||
auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
|
||||
target_surface->clear();
|
||||
const u16 width = static_cast<u16>(source_grid.width());
|
||||
const u16 height = static_cast<u16>(source_grid.height());
|
||||
|
||||
// Iterate through each LED in the corkscrew
|
||||
for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
|
||||
// Get the wrapped tile for this LED position
|
||||
Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
|
||||
|
||||
// Accumulate color from the 4 sample points with their weights
|
||||
fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
|
||||
fl::u32 total_weight = 0;
|
||||
|
||||
// Sample from each of the 4 corners of the tile
|
||||
for (fl::u8 x = 0; x < 2; x++) {
|
||||
for (fl::u8 y = 0; y < 2; y++) {
|
||||
const auto& entry = tile.at(x, y);
|
||||
vec2<u16> pos = entry.first; // position is the first element of the pair
|
||||
fl::u8 weight = entry.second; // weight is the second element of the pair
|
||||
|
||||
// Bounds check for the source grid
|
||||
if (pos.x >= 0 && pos.x < width &&
|
||||
pos.y >= 0 && pos.y < height) {
|
||||
|
||||
// Sample from the source grid
|
||||
CRGB sample_color = source_grid.at(pos.x, pos.y);
|
||||
|
||||
// Accumulate weighted color components
|
||||
r_accum += static_cast<fl::u32>(sample_color.r) * weight;
|
||||
g_accum += static_cast<fl::u32>(sample_color.g) * weight;
|
||||
b_accum += static_cast<fl::u32>(sample_color.b) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final color by dividing by total weight
|
||||
CRGB final_color = CRGB::Black;
|
||||
if (total_weight > 0) {
|
||||
final_color.r = static_cast<fl::u8>(r_accum / total_weight);
|
||||
final_color.g = static_cast<fl::u8>(g_accum / total_weight);
|
||||
final_color.b = static_cast<fl::u8>(b_accum / total_weight);
|
||||
}
|
||||
|
||||
// Store the result in the target surface at the LED index position
|
||||
auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
|
||||
if (led_idx < target_surface->size()) {
|
||||
target_surface->data()[led_idx] = final_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator implementation
|
||||
vec2f Corkscrew::iterator::operator*() const {
|
||||
return corkscrew_->at_no_wrap(static_cast<fl::u16>(position_));
|
||||
}
|
||||
|
||||
fl::ScreenMap Corkscrew::toScreenMap(float diameter) const {
|
||||
// Create a ScreenMap with the correct number of LEDs
|
||||
fl::ScreenMap screenMap(mNumLeds, diameter);
|
||||
|
||||
// For each LED index, calculate its position and set it in the ScreenMap
|
||||
for (fl::u16 i = 0; i < mNumLeds; ++i) {
|
||||
// Get the wrapped 2D position for this LED index in the cylindrical mapping
|
||||
vec2f position = at_exact(i);
|
||||
|
||||
// Set the wrapped position in the ScreenMap
|
||||
screenMap.set(i, position);
|
||||
}
|
||||
|
||||
return screenMap;
|
||||
}
|
||||
|
||||
// Enhanced surface handling methods
|
||||
fl::shared_ptr<fl::Grid<CRGB>>& Corkscrew::getOrCreateInputSurface() {
|
||||
if (!mInputSurface) {
|
||||
// Create a new Grid with cylinder dimensions using PSRAM allocation
|
||||
mInputSurface = fl::make_shared<fl::Grid<CRGB>>(mWidth, mHeight);
|
||||
}
|
||||
return mInputSurface;
|
||||
}
|
||||
|
||||
fl::Grid<CRGB>& Corkscrew::surface() {
|
||||
return *getOrCreateInputSurface();
|
||||
}
|
||||
|
||||
|
||||
fl::size Corkscrew::pixelCount() const {
|
||||
// Use variant storage if available, otherwise fall back to legacy buffer size
|
||||
if (!mPixelStorage.empty()) {
|
||||
if (mPixelStorage.template is<fl::span<CRGB>>()) {
|
||||
return mPixelStorage.template get<fl::span<CRGB>>().size();
|
||||
} else if (mPixelStorage.template is<fl::vector<CRGB, fl::allocator_psram<CRGB>>>()) {
|
||||
return mPixelStorage.template get<fl::vector<CRGB, fl::allocator_psram<CRGB>>>().size();
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to input size
|
||||
return mNumLeds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
251
libraries/FastLED/src/fl/corkscrew.h
Normal file
251
libraries/FastLED/src/fl/corkscrew.h
Normal file
@@ -0,0 +1,251 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file corkscrew.h
|
||||
* @brief Corkscrew LED strip projection and rendering
|
||||
*
|
||||
* The Corkscrew class provides a complete solution for drawing to densely-wrapped
|
||||
* helical LED strips. It maps a cylindrical coordinate system to a linear LED
|
||||
* strip, allowing you to draw on a rectangular surface and have it correctly
|
||||
* projected onto the corkscrew topology.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Create a Corkscrew with the number of turns and LEDs
|
||||
* 2. Draw patterns on the input surface using surface()
|
||||
* 3. Call draw() to map the surface to LED pixels
|
||||
* 4. Access the final LED data via rawData()
|
||||
*
|
||||
* The class handles:
|
||||
* - Automatic cylindrical dimension calculation
|
||||
* - Pixel storage (external span or internal allocation)
|
||||
* - Multi-sampling for smooth projections
|
||||
* - Gap compensation for non-continuous wrapping
|
||||
* - Iterator interface for advanced coordinate access
|
||||
*
|
||||
* Parameters:
|
||||
* - totalTurns: Number of helical turns around the cylinder
|
||||
* - numLeds: Total number of LEDs in the strip
|
||||
* - invert: Reverse the mapping direction (default: false)
|
||||
* - gapParams: Optional gap compensation for solder points in a strip.
|
||||
*/
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/shared_ptr.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/span.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class Leds;
|
||||
class ScreenMap;
|
||||
template<typename T> class Grid;
|
||||
|
||||
// Simple constexpr functions for compile-time corkscrew dimension calculation
|
||||
constexpr fl::u16 calculateCorkscrewWidth(float totalTurns, fl::u16 numLeds) {
|
||||
return static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / totalTurns));
|
||||
}
|
||||
|
||||
constexpr fl::u16 calculateCorkscrewHeight(float totalTurns, fl::u16 numLeds) {
|
||||
return (calculateCorkscrewWidth(totalTurns, numLeds) * static_cast<int>(ceil_constexpr(totalTurns)) > numLeds) ?
|
||||
static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / static_cast<float>(calculateCorkscrewWidth(totalTurns, numLeds)))) :
|
||||
static_cast<fl::u16>(ceil_constexpr(totalTurns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Struct representing gap parameters for corkscrew mapping
|
||||
*/
|
||||
struct Gap {
|
||||
int num_leds = 0; // Number of LEDs after which gap is activated, 0 = no gap
|
||||
float gap = 0.0f; // Gap value from 0 to 1, represents percentage of width unit to add
|
||||
|
||||
Gap() = default;
|
||||
Gap(float g) : num_leds(0), gap(g) {} // Backwards compatibility constructor
|
||||
Gap(int n, float g) : num_leds(n), gap(g) {} // New constructor with num_leds
|
||||
|
||||
// Rule of 5 for POD data
|
||||
Gap(const Gap &other) = default;
|
||||
Gap &operator=(const Gap &other) = default;
|
||||
Gap(Gap &&other) noexcept = default;
|
||||
Gap &operator=(Gap &&other) noexcept = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
|
||||
// a densly wrapped LED corkscrew.
|
||||
class Corkscrew {
|
||||
public:
|
||||
|
||||
// Pixel storage variants - can hold either external span or owned vector
|
||||
using PixelStorage = fl::Variant<fl::span<CRGB>, fl::vector<CRGB, fl::allocator_psram<CRGB>>>;
|
||||
|
||||
// Iterator class moved from CorkscrewState
|
||||
class iterator {
|
||||
public:
|
||||
using value_type = vec2f;
|
||||
using difference_type = fl::i32;
|
||||
using pointer = vec2f *;
|
||||
using reference = vec2f &;
|
||||
|
||||
iterator(const Corkscrew *corkscrew, fl::size position)
|
||||
: corkscrew_(corkscrew), position_(position) {}
|
||||
|
||||
vec2f operator*() const;
|
||||
|
||||
iterator &operator++() {
|
||||
++position_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator temp = *this;
|
||||
++position_;
|
||||
return temp;
|
||||
}
|
||||
|
||||
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 {
|
||||
return position_ != other.position_;
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const {
|
||||
return static_cast<difference_type>(position_) -
|
||||
static_cast<difference_type>(other.position_);
|
||||
}
|
||||
|
||||
private:
|
||||
const Corkscrew *corkscrew_;
|
||||
fl::size position_;
|
||||
};
|
||||
|
||||
// Constructors that integrate input parameters directly
|
||||
// Primary constructor with default values for invert and gapParams
|
||||
Corkscrew(float totalTurns, fl::u16 numLeds, bool invert = false, const Gap& gapParams = Gap());
|
||||
|
||||
// Constructor with external pixel buffer - these pixels will be drawn to directly
|
||||
Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert = false, const Gap& gapParams = Gap());
|
||||
|
||||
|
||||
Corkscrew(const Corkscrew &) = default;
|
||||
Corkscrew(Corkscrew &&) = default;
|
||||
|
||||
|
||||
// Caching control
|
||||
void setCachingEnabled(bool enabled);
|
||||
|
||||
// Essential API - Core functionality
|
||||
fl::u16 cylinderWidth() const { return mWidth; }
|
||||
fl::u16 cylinderHeight() const { return mHeight; }
|
||||
|
||||
// Enhanced surface handling with shared_ptr
|
||||
// Note: Input surface will be created on first call
|
||||
fl::shared_ptr<fl::Grid<CRGB>>& getOrCreateInputSurface();
|
||||
|
||||
// Draw like a regular rectangle surface - access input surface directly
|
||||
fl::Grid<CRGB>& surface();
|
||||
|
||||
|
||||
// Draw the corkscrew by reading from the internal surface and populating LED pixels
|
||||
void draw(bool use_multi_sampling = true);
|
||||
|
||||
// Pixel storage access - works with both external and owned pixels
|
||||
// This represents the pixels that will be drawn after draw() is called
|
||||
CRGB* rawData();
|
||||
|
||||
// Returns span of pixels that will be written to when draw() is called
|
||||
fl::span<CRGB> data();
|
||||
|
||||
fl::size pixelCount() const;
|
||||
// Create and return a fully constructed ScreenMap for this corkscrew
|
||||
// Each LED index will be mapped to its exact position on the cylindrical surface
|
||||
fl::ScreenMap toScreenMap(float diameter = 0.5f) const;
|
||||
|
||||
// STL-style container interface
|
||||
fl::size size() const;
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
iterator end() { return iterator(this, size()); }
|
||||
|
||||
// Non-essential API - Lower level access
|
||||
vec2f at_no_wrap(fl::u16 i) const;
|
||||
vec2f at_exact(fl::u16 i) const;
|
||||
Tile2x2_u8_wrap at_wrap(float i) const;
|
||||
|
||||
// Clear all buffers and free memory
|
||||
void clear();
|
||||
|
||||
// Fill the input surface with a color
|
||||
void fillInputSurface(const CRGB& color);
|
||||
|
||||
|
||||
private:
|
||||
// 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;
|
||||
|
||||
// Read from fl::Grid<CRGB> object and populate our internal rectangular buffer
|
||||
// by sampling from the XY coordinates mapped to each corkscrew LED position
|
||||
// use_multi_sampling = true will use multi-sampling to sample from the source grid,
|
||||
// this will give a little bit better accuracy and the screenmap will be more accurate.
|
||||
void readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling = true);
|
||||
|
||||
// Read from rectangular buffer using multi-sampling and store in target grid
|
||||
// Uses Tile2x2_u8_wrap for sub-pixel accurate sampling with proper blending
|
||||
void readFromMulti(const fl::Grid<CRGB>& target_grid) const;
|
||||
|
||||
// Initialize the rectangular buffer if not already done
|
||||
void initializeBuffer() const;
|
||||
|
||||
// Initialize the cache if not already done and caching is enabled
|
||||
void initializeCache() const;
|
||||
|
||||
// Calculate the tile at position i without using cache
|
||||
Tile2x2_u8_wrap calculateTileAtWrap(float i) const;
|
||||
|
||||
// Core corkscrew parameters (moved from CorkscrewInput)
|
||||
float mTotalTurns = 19.0f; // Total turns of the corkscrew
|
||||
fl::u16 mNumLeds = 144; // Number of LEDs
|
||||
Gap mGapParams; // Gap parameters for gap accounting
|
||||
bool mInvert = false; // If true, reverse the mapping order
|
||||
|
||||
// Cylindrical mapping dimensions (moved from CorkscrewState)
|
||||
fl::u16 mWidth = 0; // Width of cylindrical map (circumference of one turn)
|
||||
fl::u16 mHeight = 0; // Height of cylindrical map (total vertical segments)
|
||||
|
||||
// Enhanced pixel storage - variant supports both external and owned pixels
|
||||
PixelStorage mPixelStorage;
|
||||
bool mOwnsPixels = false; // Track whether we own the pixel data
|
||||
|
||||
// Input surface for drawing operations
|
||||
fl::shared_ptr<fl::Grid<CRGB>> mInputSurface;
|
||||
|
||||
// Caching for Tile2x2_u8_wrap objects
|
||||
mutable fl::vector<Tile2x2_u8_wrap> mTileCache;
|
||||
mutable bool mCacheInitialized = false;
|
||||
bool mCachingEnabled = true; // Default to enabled
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/crgb_hsv16.cpp
Normal file
31
libraries/FastLED/src/fl/crgb_hsv16.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/// @file crgb_hsv16.cpp
|
||||
/// HSV16-dependent methods for CRGB - only linked when HSV16 functionality is used
|
||||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "crgb.h"
|
||||
#include "fl/hsv16.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
CRGB CRGB::colorBoost(fl::EaseType saturation_function, fl::EaseType luminance_function) const {
|
||||
fl::HSV16 hsv(*this);
|
||||
return hsv.colorBoost(saturation_function, luminance_function);
|
||||
}
|
||||
|
||||
void CRGB::colorBoost(const CRGB* src, CRGB* dst, size_t count, fl::EaseType saturation_function, fl::EaseType luminance_function) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
dst[i] = src[i].colorBoost(saturation_function, luminance_function);
|
||||
}
|
||||
}
|
||||
|
||||
fl::HSV16 CRGB::toHSV16() const {
|
||||
return fl::HSV16(*this);
|
||||
}
|
||||
|
||||
// Constructor implementation for HSV16 -> CRGB automatic conversion
|
||||
CRGB::CRGB(const fl::HSV16& rhs) {
|
||||
*this = rhs.ToRGB();
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
25
libraries/FastLED/src/fl/cstddef.h
Normal file
25
libraries/FastLED/src/fl/cstddef.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
#include <cstddef> // ok include
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
// FastLED equivalent of std::nullptr_t
|
||||
typedef decltype(nullptr) nullptr_t;
|
||||
|
||||
// FastLED equivalent of std::size_t and std::ptrdiff_t
|
||||
// These are defined here for completeness but may already exist elsewhere
|
||||
#ifndef FL_SIZE_T_DEFINED
|
||||
#define FL_SIZE_T_DEFINED
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
#endif
|
||||
|
||||
#ifndef FL_PTRDIFF_T_DEFINED
|
||||
#define FL_PTRDIFF_T_DEFINED
|
||||
typedef __PTRDIFF_TYPE__ ptrdiff_t;
|
||||
#endif
|
||||
|
||||
} // namespace fl
|
||||
67
libraries/FastLED/src/fl/dbg.h
Normal file
67
libraries/FastLED/src/fl/dbg.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/strstream.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
// Forward declaration to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled
|
||||
// This prevents ~5KB memory bloat for simple applications
|
||||
#ifndef FL_DBG_PRINTLN_DECLARED
|
||||
#define FL_DBG_PRINTLN_DECLARED
|
||||
namespace fl {
|
||||
void println(const char* str);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
// ".build/src/fl/dbg.h" -> "src/fl/dbg.h"
|
||||
// "blah/blah/blah.h" -> "blah.h"
|
||||
inline const char *fastled_file_offset(const char *file) {
|
||||
const char *p = file;
|
||||
const char *last_slash = nullptr;
|
||||
|
||||
while (*p) {
|
||||
if (p[0] == 's' && p[1] == 'r' && p[2] == 'c' && p[3] == '/') {
|
||||
return p; // Skip past "src/"
|
||||
}
|
||||
if (*p == '/') { // fallback to using last slash
|
||||
last_slash = p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
// If "src/" not found but we found at least one slash, return after the
|
||||
// last slash
|
||||
if (last_slash) {
|
||||
return last_slash + 1;
|
||||
}
|
||||
return file; // If no slashes found at all, return original path
|
||||
}
|
||||
} // namespace fl
|
||||
|
||||
#if __EMSCRIPTEN__ || !defined(RELEASE) || defined(FASTLED_TESTING)
|
||||
#define FASTLED_FORCE_DBG 1
|
||||
#endif
|
||||
|
||||
// Debug printing: Enable only when explicitly requested to avoid ~5KB memory bloat
|
||||
#if !defined(FASTLED_FORCE_DBG) || !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// By default, debug printing is disabled to prevent memory bloat in simple applications
|
||||
#define FASTLED_HAS_DBG 0
|
||||
#define _FASTLED_DGB(X) do { if (false) { fl::println(""); } } while(0) // No-op that handles << operator
|
||||
#else
|
||||
// Explicit debug mode enabled - uses fl::println()
|
||||
#define FASTLED_HAS_DBG 1
|
||||
#define _FASTLED_DGB(X) \
|
||||
fl::println( \
|
||||
(fl::StrStream() << (fl::fastled_file_offset(__FILE__)) \
|
||||
<< "(" << int(__LINE__) << "): " << X) \
|
||||
.c_str())
|
||||
#endif
|
||||
|
||||
#define FASTLED_DBG(X) _FASTLED_DGB(X)
|
||||
|
||||
#ifndef FASTLED_DBG_IF
|
||||
#define FASTLED_DBG_IF(COND, MSG) \
|
||||
if (COND) \
|
||||
FASTLED_DBG(MSG)
|
||||
#endif // FASTLED_DBG_IF
|
||||
28
libraries/FastLED/src/fl/deprecated.h
Normal file
28
libraries/FastLED/src/fl/deprecated.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__clang__)
|
||||
// Clang: Do not mark classes as deprecated
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#endif
|
||||
#elif defined(__GNUC__) // GCC (but not Clang)
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#define FASTLED_DEPRECATED_CLASS(msg) __attribute__((deprecated(msg)))
|
||||
#else
|
||||
#define FASTLED_DEPRECATED(msg) __attribute__((deprecated))
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#endif
|
||||
#endif
|
||||
#else // Other compilers
|
||||
#ifndef FASTLED_DEPRECATED
|
||||
#define FASTLED_DEPRECATED(msg)
|
||||
#endif
|
||||
#define FASTLED_DEPRECATED_CLASS(msg)
|
||||
#endif
|
||||
|
||||
|
||||
#define FL_DEPRECATED(msg) FASTLED_DEPRECATED(msg)
|
||||
#define FL_DEPRECATED_CLASS(msg) FASTLED_DEPRECATED_CLASS(msg)
|
||||
390
libraries/FastLED/src/fl/deque.h
Normal file
390
libraries/FastLED/src/fl/deque.h
Normal file
@@ -0,0 +1,390 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "fl/allocator.h"
|
||||
#include "fl/initializer_list.h"
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/type_traits.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T, typename Allocator = fl::allocator<T>>
|
||||
class deque {
|
||||
private:
|
||||
T* mData = nullptr;
|
||||
fl::size mCapacity = 0;
|
||||
fl::size mSize = 0;
|
||||
fl::size mFront = 0; // Index of the front element
|
||||
Allocator mAlloc;
|
||||
|
||||
static const fl::size kInitialCapacity = 8;
|
||||
|
||||
void ensure_capacity(fl::size min_capacity) {
|
||||
if (mCapacity >= min_capacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
fl::size new_capacity = mCapacity == 0 ? kInitialCapacity : mCapacity * 2;
|
||||
while (new_capacity < min_capacity) {
|
||||
new_capacity *= 2;
|
||||
}
|
||||
|
||||
T* new_data = mAlloc.allocate(new_capacity);
|
||||
if (!new_data) {
|
||||
return; // Allocation failed
|
||||
}
|
||||
|
||||
// Copy existing elements to new buffer in linear order
|
||||
for (fl::size i = 0; i < mSize; ++i) {
|
||||
fl::size old_idx = (mFront + i) % mCapacity;
|
||||
mAlloc.construct(&new_data[i], fl::move(mData[old_idx]));
|
||||
mAlloc.destroy(&mData[old_idx]);
|
||||
}
|
||||
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
|
||||
mData = new_data;
|
||||
mCapacity = new_capacity;
|
||||
mFront = 0; // Reset front to 0 after reallocation
|
||||
}
|
||||
|
||||
fl::size get_index(fl::size logical_index) const {
|
||||
return (mFront + logical_index) % mCapacity;
|
||||
}
|
||||
|
||||
public:
|
||||
// Iterator implementation
|
||||
class iterator {
|
||||
private:
|
||||
deque* mDeque;
|
||||
fl::size mIndex;
|
||||
|
||||
public:
|
||||
iterator(deque* dq, fl::size index) : mDeque(dq), mIndex(index) {}
|
||||
|
||||
T& operator*() const {
|
||||
return (*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return &(*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
++mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator temp = *this;
|
||||
++mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
iterator& operator--() {
|
||||
--mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) {
|
||||
iterator temp = *this;
|
||||
--mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) const {
|
||||
return mDeque == other.mDeque && mIndex == other.mIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
class const_iterator {
|
||||
private:
|
||||
const deque* mDeque;
|
||||
fl::size mIndex;
|
||||
|
||||
public:
|
||||
const_iterator(const deque* dq, fl::size index) : mDeque(dq), mIndex(index) {}
|
||||
|
||||
const T& operator*() const {
|
||||
return (*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
const T* operator->() const {
|
||||
return &(*mDeque)[mIndex];
|
||||
}
|
||||
|
||||
const_iterator& operator++() {
|
||||
++mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator++(int) {
|
||||
const_iterator temp = *this;
|
||||
++mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
const_iterator& operator--() {
|
||||
--mIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator--(int) {
|
||||
const_iterator temp = *this;
|
||||
--mIndex;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& other) const {
|
||||
return mDeque == other.mDeque && mIndex == other.mIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const const_iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// Constructors
|
||||
deque() : mData(nullptr), mCapacity(0), mSize(0), mFront(0) {}
|
||||
|
||||
explicit deque(fl::size count, const T& value = T()) : deque() {
|
||||
resize(count, value);
|
||||
}
|
||||
|
||||
deque(const deque& other) : deque() {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
deque(deque&& other) : deque() {
|
||||
*this = fl::move(other);
|
||||
}
|
||||
|
||||
deque(fl::initializer_list<T> init) : deque() {
|
||||
for (const auto& value : init) {
|
||||
push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~deque() {
|
||||
clear();
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
// Assignment operators
|
||||
deque& operator=(const deque& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
for (fl::size i = 0; i < other.size(); ++i) {
|
||||
push_back(other[i]);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
deque& operator=(deque&& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
if (mData) {
|
||||
mAlloc.deallocate(mData, mCapacity);
|
||||
}
|
||||
|
||||
mData = other.mData;
|
||||
mCapacity = other.mCapacity;
|
||||
mSize = other.mSize;
|
||||
mFront = other.mFront;
|
||||
mAlloc = other.mAlloc;
|
||||
|
||||
other.mData = nullptr;
|
||||
other.mCapacity = 0;
|
||||
other.mSize = 0;
|
||||
other.mFront = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Element access
|
||||
T& operator[](fl::size index) {
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
const T& operator[](fl::size index) const {
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
T& at(fl::size index) {
|
||||
if (index >= mSize) {
|
||||
// Handle bounds error - in embedded context, we'll just return the first element
|
||||
// In a real implementation, this might throw an exception
|
||||
return mData[mFront];
|
||||
}
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
const T& at(fl::size index) const {
|
||||
if (index >= mSize) {
|
||||
// Handle bounds error - in embedded context, we'll just return the first element
|
||||
return mData[mFront];
|
||||
}
|
||||
return mData[get_index(index)];
|
||||
}
|
||||
|
||||
T& front() {
|
||||
return mData[mFront];
|
||||
}
|
||||
|
||||
const T& front() const {
|
||||
return mData[mFront];
|
||||
}
|
||||
|
||||
T& back() {
|
||||
return mData[get_index(mSize - 1)];
|
||||
}
|
||||
|
||||
const T& back() const {
|
||||
return mData[get_index(mSize - 1)];
|
||||
}
|
||||
|
||||
// Iterators
|
||||
iterator begin() {
|
||||
return iterator(this, 0);
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return const_iterator(this, 0);
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator(this, mSize);
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return const_iterator(this, mSize);
|
||||
}
|
||||
|
||||
// Capacity
|
||||
bool empty() const {
|
||||
return mSize == 0;
|
||||
}
|
||||
|
||||
fl::size size() const {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
fl::size capacity() const {
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
void clear() {
|
||||
while (!empty()) {
|
||||
pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(const T& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
fl::size back_index = get_index(mSize);
|
||||
mAlloc.construct(&mData[back_index], value);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_back(T&& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
fl::size back_index = get_index(mSize);
|
||||
mAlloc.construct(&mData[back_index], fl::move(value));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(const T& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
mFront = (mFront - 1 + mCapacity) % mCapacity;
|
||||
mAlloc.construct(&mData[mFront], value);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(T&& value) {
|
||||
ensure_capacity(mSize + 1);
|
||||
mFront = (mFront - 1 + mCapacity) % mCapacity;
|
||||
mAlloc.construct(&mData[mFront], fl::move(value));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
if (mSize > 0) {
|
||||
fl::size back_index = get_index(mSize - 1);
|
||||
mAlloc.destroy(&mData[back_index]);
|
||||
--mSize;
|
||||
}
|
||||
}
|
||||
|
||||
void pop_front() {
|
||||
if (mSize > 0) {
|
||||
mAlloc.destroy(&mData[mFront]);
|
||||
mFront = (mFront + 1) % mCapacity;
|
||||
--mSize;
|
||||
}
|
||||
}
|
||||
|
||||
void resize(fl::size new_size) {
|
||||
resize(new_size, T());
|
||||
}
|
||||
|
||||
void resize(fl::size new_size, const T& value) {
|
||||
if (new_size > mSize) {
|
||||
// Add elements
|
||||
ensure_capacity(new_size);
|
||||
while (mSize < new_size) {
|
||||
push_back(value);
|
||||
}
|
||||
} else if (new_size < mSize) {
|
||||
// Remove elements
|
||||
while (mSize > new_size) {
|
||||
pop_back();
|
||||
}
|
||||
}
|
||||
// If new_size == mSize, do nothing
|
||||
}
|
||||
|
||||
void swap(deque& other) {
|
||||
if (this != &other) {
|
||||
T* temp_data = mData;
|
||||
fl::size temp_capacity = mCapacity;
|
||||
fl::size temp_size = mSize;
|
||||
fl::size temp_front = mFront;
|
||||
Allocator temp_alloc = mAlloc;
|
||||
|
||||
mData = other.mData;
|
||||
mCapacity = other.mCapacity;
|
||||
mSize = other.mSize;
|
||||
mFront = other.mFront;
|
||||
mAlloc = other.mAlloc;
|
||||
|
||||
other.mData = temp_data;
|
||||
other.mCapacity = temp_capacity;
|
||||
other.mSize = temp_size;
|
||||
other.mFront = temp_front;
|
||||
other.mAlloc = temp_alloc;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Convenience typedef for the most common use case
|
||||
typedef deque<int> deque_int;
|
||||
typedef deque<float> deque_float;
|
||||
typedef deque<double> deque_double;
|
||||
|
||||
} // namespace fl
|
||||
53
libraries/FastLED/src/fl/dll.h
Normal file
53
libraries/FastLED/src/fl/dll.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
/// @file dll.h
|
||||
/// FastLED dynamic library interface - lightweight header for external callers
|
||||
|
||||
#ifndef FASTLED_BUILD_EXPORTS
|
||||
#define FASTLED_BUILD_EXPORTS 0
|
||||
#endif
|
||||
|
||||
#if FASTLED_BUILD_EXPORTS
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Call the sketch's setup() function
|
||||
/// @note This is the C ABI export for external sketch runners
|
||||
FASTLED_EXPORT void sketch_setup(void);
|
||||
|
||||
/// Call the sketch's loop() function
|
||||
/// @note This is the C ABI export for external sketch runners
|
||||
FASTLED_EXPORT void sketch_loop(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// ================================================================================================
|
||||
// IMPLEMENTATIONS (when building FastLED as shared library)
|
||||
// ================================================================================================
|
||||
|
||||
#ifdef FASTLED_LIBRARY_SHARED
|
||||
|
||||
#ifdef __cplusplus
|
||||
// Forward declarations - provided by sketch
|
||||
extern void setup();
|
||||
extern void loop();
|
||||
|
||||
// Provide implementations for the exported functions
|
||||
FASTLED_EXPORT void sketch_setup() {
|
||||
setup();
|
||||
}
|
||||
|
||||
FASTLED_EXPORT void sketch_loop() {
|
||||
loop();
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // FASTLED_LIBRARY_SHARED
|
||||
|
||||
#endif // FASTLED_BUILD_EXPORTS
|
||||
188
libraries/FastLED/src/fl/downscale.cpp
Normal file
188
libraries/FastLED/src/fl/downscale.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
// BETA - NOT TESTED!!!
|
||||
// VIBE CODED WITH AI
|
||||
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshift-count-overflow"
|
||||
|
||||
namespace fl {
|
||||
|
||||
void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight,
|
||||
CRGB *dst) {
|
||||
fl::u16 dstWidth = srcWidth / 2;
|
||||
fl::u16 dstHeight = srcHeight / 2;
|
||||
|
||||
for (fl::u16 y = 0; y < dstHeight; ++y) {
|
||||
for (fl::u16 x = 0; x < dstWidth; ++x) {
|
||||
// Map to top-left of the 2x2 block in source
|
||||
fl::u16 srcX = x * 2;
|
||||
fl::u16 srcY = y * 2;
|
||||
|
||||
// Fetch 2x2 block
|
||||
const CRGB &p00 = src[(srcY)*srcWidth + (srcX)];
|
||||
const CRGB &p10 = src[(srcY)*srcWidth + (srcX + 1)];
|
||||
const CRGB &p01 = src[(srcY + 1) * srcWidth + (srcX)];
|
||||
const CRGB &p11 = src[(srcY + 1) * srcWidth + (srcX + 1)];
|
||||
|
||||
// Average each color channel
|
||||
fl::u16 r =
|
||||
(p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
|
||||
fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
|
||||
fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
|
||||
|
||||
// Store result
|
||||
dst[y * dstWidth + x] = CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
fl::u16 dstWidth = dstXY.getWidth();
|
||||
fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
FASTLED_ASSERT(srcXY.getWidth() == dstXY.getWidth() * 2,
|
||||
"Source width must be double the destination width");
|
||||
FASTLED_ASSERT(srcXY.getHeight() == dstXY.getHeight() * 2,
|
||||
"Source height must be double the destination height");
|
||||
|
||||
for (fl::u16 y = 0; y < dstHeight; ++y) {
|
||||
for (fl::u16 x = 0; x < dstWidth; ++x) {
|
||||
// Map to top-left of the 2x2 block in source
|
||||
fl::u16 srcX = x * 2;
|
||||
fl::u16 srcY = y * 2;
|
||||
|
||||
// Fetch 2x2 block
|
||||
const CRGB &p00 = src[srcXY.mapToIndex(srcX, srcY)];
|
||||
const CRGB &p10 = src[srcXY.mapToIndex(srcX + 1, srcY)];
|
||||
const CRGB &p01 = src[srcXY.mapToIndex(srcX, srcY + 1)];
|
||||
const CRGB &p11 = src[srcXY.mapToIndex(srcX + 1, srcY + 1)];
|
||||
|
||||
// Average each color channel
|
||||
fl::u16 r =
|
||||
(p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding
|
||||
fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4;
|
||||
fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4;
|
||||
|
||||
// Store result
|
||||
dst[dstXY.mapToIndex(x, y)] =
|
||||
CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
const fl::u16 srcWidth = srcXY.getWidth();
|
||||
const fl::u16 srcHeight = srcXY.getHeight();
|
||||
const fl::u16 dstWidth = dstXY.getWidth();
|
||||
const fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
const fl::u32 FP_ONE = 256; // Q8.8 fixed-point multiplier
|
||||
|
||||
FASTLED_ASSERT(dstWidth <= srcWidth,
|
||||
"Destination width must be <= source width");
|
||||
FASTLED_ASSERT(dstHeight <= srcHeight,
|
||||
"Destination height must be <= source height");
|
||||
|
||||
for (fl::u16 dy = 0; dy < dstHeight; ++dy) {
|
||||
// Fractional boundaries in Q8.8
|
||||
fl::u32 dstY0 = (dy * srcHeight * FP_ONE) / dstHeight;
|
||||
fl::u32 dstY1 = ((dy + 1) * srcHeight * FP_ONE) / dstHeight;
|
||||
|
||||
for (fl::u16 dx = 0; dx < dstWidth; ++dx) {
|
||||
fl::u32 dstX0 = (dx * srcWidth * FP_ONE) / dstWidth;
|
||||
fl::u32 dstX1 = ((dx + 1) * srcWidth * FP_ONE) / dstWidth;
|
||||
|
||||
fl::u64 rSum = 0, gSum = 0, bSum = 0;
|
||||
fl::u32 totalWeight = 0;
|
||||
|
||||
// Find covered source pixels
|
||||
fl::u16 srcY_start = dstY0 / FP_ONE;
|
||||
fl::u16 srcY_end = (dstY1 + FP_ONE - 1) / FP_ONE; // ceil
|
||||
|
||||
fl::u16 srcX_start = dstX0 / FP_ONE;
|
||||
fl::u16 srcX_end = (dstX1 + FP_ONE - 1) / FP_ONE; // ceil
|
||||
|
||||
for (fl::u16 sy = srcY_start; sy < srcY_end; ++sy) {
|
||||
// Calculate vertical overlap in Q8.8
|
||||
fl::u32 sy0 = sy * FP_ONE;
|
||||
fl::u32 sy1 = (sy + 1) * FP_ONE;
|
||||
fl::u32 y_overlap = MIN(dstY1, sy1) - MAX(dstY0, sy0);
|
||||
if (y_overlap == 0)
|
||||
continue;
|
||||
|
||||
for (fl::u16 sx = srcX_start; sx < srcX_end; ++sx) {
|
||||
fl::u32 sx0 = sx * FP_ONE;
|
||||
fl::u32 sx1 = (sx + 1) * FP_ONE;
|
||||
fl::u32 x_overlap = MIN(dstX1, sx1) - MAX(dstX0, sx0);
|
||||
if (x_overlap == 0)
|
||||
continue;
|
||||
|
||||
fl::u32 weight = (x_overlap * y_overlap + (FP_ONE >> 1)) >>
|
||||
8; // Q8.8 * Q8.8 → Q16.16 → Q8.8
|
||||
|
||||
const CRGB &p = src[srcXY.mapToIndex(sx, sy)];
|
||||
rSum += p.r * weight;
|
||||
gSum += p.g * weight;
|
||||
bSum += p.b * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Final division, rounding
|
||||
fl::u8 r =
|
||||
totalWeight ? (rSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
fl::u8 g =
|
||||
totalWeight ? (gSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
fl::u8 b =
|
||||
totalWeight ? (bSum + (totalWeight >> 1)) / totalWeight : 0;
|
||||
|
||||
dst[dstXY.mapToIndex(dx, dy)] = CRGB(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY) {
|
||||
fl::u16 srcWidth = srcXY.getWidth();
|
||||
fl::u16 srcHeight = srcXY.getHeight();
|
||||
fl::u16 dstWidth = dstXY.getWidth();
|
||||
fl::u16 dstHeight = dstXY.getHeight();
|
||||
|
||||
FASTLED_ASSERT(dstWidth <= srcWidth,
|
||||
"Destination width must be <= source width");
|
||||
FASTLED_ASSERT(dstHeight <= srcHeight,
|
||||
"Destination height must be <= source height");
|
||||
const bool destination_is_half_of_source =
|
||||
(dstWidth * 2 == srcWidth) && (dstHeight * 2 == srcHeight);
|
||||
// Attempt to use the downscaleHalf function if the destination is half the
|
||||
// size of the source.
|
||||
if (destination_is_half_of_source) {
|
||||
const bool both_rectangles = (srcXY.getType() == XYMap::kLineByLine) &&
|
||||
(dstXY.getType() == XYMap::kLineByLine);
|
||||
if (both_rectangles) {
|
||||
// If both source and destination are rectangular, we can use the
|
||||
// optimized version
|
||||
downscaleHalf(src, srcWidth, srcHeight, dst);
|
||||
} else {
|
||||
// Otherwise, we need to use the mapped version
|
||||
downscaleHalf(src, srcXY, dst, dstXY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
downscaleArbitrary(src, srcXY, dst, dstXY);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
33
libraries/FastLED/src/fl/downscale.h
Normal file
33
libraries/FastLED/src/fl/downscale.h
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Experimental bilinearn downscaling algorithm. Not tested yet and completely
|
||||
"vibe-coded" by ai.
|
||||
|
||||
If you use this and find an issue then please report it.
|
||||
*/
|
||||
|
||||
#include "fl/int.h"
|
||||
#include "crgb.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class XYMap;
|
||||
|
||||
void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
|
||||
// Optimized versions for downscaling by 50%. This is here for testing purposes
|
||||
// mostly. You should prefer to use downscale(...) instead of calling these
|
||||
// functions. It's important to note that downscale(...) will invoke
|
||||
// downscaleHalf(...) automatically when the source and destination are half the
|
||||
// size of each other.
|
||||
void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight,
|
||||
CRGB *dst);
|
||||
void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst,
|
||||
const XYMap &dstXY);
|
||||
|
||||
} // namespace fl
|
||||
7
libraries/FastLED/src/fl/draw_mode.h
Normal file
7
libraries/FastLED/src/fl/draw_mode.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace fl {
|
||||
|
||||
enum DrawMode { DRAW_MODE_OVERWRITE, DRAW_MODE_BLEND_BY_MAX_BRIGHTNESS };
|
||||
|
||||
} // namespace fl
|
||||
77
libraries/FastLED/src/fl/draw_visitor.h
Normal file
77
libraries/FastLED/src/fl/draw_visitor.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/gradient.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fl/move.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Draws a fl::u8 value to a CRGB array, blending it with the existing color.
|
||||
struct XYDrawComposited {
|
||||
XYDrawComposited(const CRGB &color, const XYMap &xymap, CRGB *out);
|
||||
|
||||
// Copy constructor (assignment deleted due to const members)
|
||||
XYDrawComposited(const XYDrawComposited &other) = default;
|
||||
XYDrawComposited &operator=(const XYDrawComposited &other) = delete;
|
||||
|
||||
// Move constructor (assignment deleted due to const members)
|
||||
XYDrawComposited(XYDrawComposited &&other) noexcept
|
||||
: mColor(fl::move(other.mColor)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {}
|
||||
XYDrawComposited &operator=(XYDrawComposited &&other) noexcept = delete;
|
||||
|
||||
void draw(const vec2<fl::u16> &pt, fl::u32 index, fl::u8 value);
|
||||
const CRGB mColor;
|
||||
const XYMap mXYMap;
|
||||
CRGB *mOut;
|
||||
};
|
||||
|
||||
struct XYDrawGradient {
|
||||
XYDrawGradient(const Gradient &gradient, const XYMap &xymap, CRGB *out);
|
||||
|
||||
// Copy constructor (assignment deleted due to const members)
|
||||
XYDrawGradient(const XYDrawGradient &other) = default;
|
||||
XYDrawGradient &operator=(const XYDrawGradient &other) = delete;
|
||||
|
||||
// Move constructor (assignment deleted due to const members)
|
||||
XYDrawGradient(XYDrawGradient &&other) noexcept
|
||||
: mGradient(fl::move(other.mGradient)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {}
|
||||
XYDrawGradient &operator=(XYDrawGradient &&other) noexcept = delete;
|
||||
|
||||
void draw(const vec2<fl::u16> &pt, fl::u32 index, fl::u8 value);
|
||||
const Gradient mGradient;
|
||||
const XYMap mXYMap;
|
||||
CRGB *mOut;
|
||||
};
|
||||
|
||||
inline XYDrawComposited::XYDrawComposited(const CRGB &color, const XYMap &xymap,
|
||||
CRGB *out)
|
||||
: mColor(color), mXYMap(xymap), mOut(out) {}
|
||||
|
||||
inline void XYDrawComposited::draw(const vec2<fl::u16> &pt, fl::u32 index,
|
||||
fl::u8 value) {
|
||||
FASTLED_UNUSED(pt);
|
||||
CRGB &c = mOut[index];
|
||||
CRGB blended = mColor;
|
||||
blended.fadeToBlackBy(255 - value);
|
||||
c = CRGB::blendAlphaMaxChannel(blended, c);
|
||||
}
|
||||
|
||||
inline XYDrawGradient::XYDrawGradient(const Gradient &gradient,
|
||||
const XYMap &xymap, CRGB *out)
|
||||
: mGradient(gradient), mXYMap(xymap), mOut(out) {}
|
||||
|
||||
inline void XYDrawGradient::draw(const vec2<fl::u16> &pt, fl::u32 index,
|
||||
fl::u8 value) {
|
||||
FASTLED_UNUSED(pt);
|
||||
CRGB c = mGradient.colorAt(value);
|
||||
mOut[index] = c;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
339
libraries/FastLED/src/fl/ease.cpp
Normal file
339
libraries/FastLED/src/fl/ease.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#ifndef FASTLED_INTERNAL
|
||||
#define FASTLED_INTERNAL
|
||||
#endif
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "fl/ease.h"
|
||||
#include "lib8tion.h" // This is the problematic header that's hard to include
|
||||
|
||||
#include "fl/map_range.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/sin32.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction
|
||||
// This table converts linear 8-bit values to gamma-corrected 16-bit values
|
||||
// using a gamma curve of 2.8 (commonly used for LED brightness correction)
|
||||
const u16 gamma_2_8[256] FL_PROGMEM = {
|
||||
0, 0, 0, 1, 1, 2, 4, 6, 8, 11,
|
||||
14, 18, 23, 29, 35, 41, 49, 57, 67, 77,
|
||||
88, 99, 112, 126, 141, 156, 173, 191, 210, 230,
|
||||
251, 274, 297, 322, 348, 375, 404, 433, 464, 497,
|
||||
531, 566, 602, 640, 680, 721, 763, 807, 853, 899,
|
||||
948, 998, 1050, 1103, 1158, 1215, 1273, 1333, 1394, 1458,
|
||||
1523, 1590, 1658, 1729, 1801, 1875, 1951, 2029, 2109, 2190,
|
||||
2274, 2359, 2446, 2536, 2627, 2720, 2816, 2913, 3012, 3114,
|
||||
3217, 3323, 3431, 3541, 3653, 3767, 3883, 4001, 4122, 4245,
|
||||
4370, 4498, 4627, 4759, 4893, 5030, 5169, 5310, 5453, 5599,
|
||||
5747, 5898, 6051, 6206, 6364, 6525, 6688, 6853, 7021, 7191,
|
||||
7364, 7539, 7717, 7897, 8080, 8266, 8454, 8645, 8838, 9034,
|
||||
9233, 9434, 9638, 9845, 10055, 10267, 10482, 10699, 10920, 11143,
|
||||
11369, 11598, 11829, 12064, 12301, 12541, 12784, 13030, 13279, 13530,
|
||||
13785, 14042, 14303, 14566, 14832, 15102, 15374, 15649, 15928, 16209,
|
||||
16493, 16781, 17071, 17365, 17661, 17961, 18264, 18570, 18879, 19191,
|
||||
19507, 19825, 20147, 20472, 20800, 21131, 21466, 21804, 22145, 22489,
|
||||
22837, 23188, 23542, 23899, 24260, 24625, 24992, 25363, 25737, 26115,
|
||||
26496, 26880, 27268, 27659, 28054, 28452, 28854, 29259, 29667, 30079,
|
||||
30495, 30914, 31337, 31763, 32192, 32626, 33062, 33503, 33947, 34394,
|
||||
34846, 35300, 35759, 36221, 36687, 37156, 37629, 38106, 38586, 39071,
|
||||
39558, 40050, 40545, 41045, 41547, 42054, 42565, 43079, 43597, 44119,
|
||||
44644, 45174, 45707, 46245, 46786, 47331, 47880, 48432, 48989, 49550,
|
||||
50114, 50683, 51255, 51832, 52412, 52996, 53585, 54177, 54773, 55374,
|
||||
55978, 56587, 57199, 57816, 58436, 59061, 59690, 60323, 60960, 61601,
|
||||
62246, 62896, 63549, 64207, 64869, 65535};
|
||||
|
||||
// 8-bit easing functions
|
||||
u8 easeInQuad8(u8 i) {
|
||||
// Simple quadratic ease-in: i^2 scaled to 8-bit range
|
||||
// Using scale8(i, i) which computes (i * i) / 255
|
||||
return scale8(i, i);
|
||||
}
|
||||
|
||||
u8 easeInOutQuad8(u8 i) {
|
||||
constexpr u16 MAX = 0xFF; // 255
|
||||
constexpr u16 HALF = (MAX + 1) >> 1; // 128
|
||||
constexpr u16 DENOM = MAX; // divisor for scaling
|
||||
constexpr u16 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (i < HALF) {
|
||||
// first half: y = 2·(i/MAX)² → y_i = 2·i² / MAX
|
||||
u32 t = i;
|
||||
u32 num = 2 * t * t + ROUND; // 2*i², +half for rounding
|
||||
return u8(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − 2·(1−i/MAX)²
|
||||
// → y_i = MAX − (2·(MAX−i)² / MAX)
|
||||
u32 d = MAX - i;
|
||||
u32 num = 2 * d * d + ROUND; // 2*(MAX−i)², +half for rounding
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u8 easeInOutCubic8(u8 i) {
|
||||
constexpr u16 MAX = 0xFF; // 255
|
||||
constexpr u16 HALF = (MAX + 1) >> 1; // 128
|
||||
constexpr u32 DENOM = (u32)MAX * MAX; // 255*255 = 65025
|
||||
constexpr u32 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (i < HALF) {
|
||||
// first half: y = 4·(i/MAX)³ → y_i = 4·i³ / MAX²
|
||||
u32 ii = i;
|
||||
u32 cube = ii * ii * ii; // i³
|
||||
u32 num = 4 * cube + ROUND; // 4·i³, +half denom for rounding
|
||||
return u8(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − ((−2·t+2)³)/2
|
||||
// where t = i/MAX; equivalently:
|
||||
// y_i = MAX − (4·(MAX−i)³ / MAX²)
|
||||
u32 d = MAX - i;
|
||||
u32 cube = d * d * d; // (MAX−i)³
|
||||
u32 num = 4 * cube + ROUND;
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u8 easeOutQuad8(u8 i) {
|
||||
// ease-out is the inverse of ease-in: 1 - (1-t)²
|
||||
// For 8-bit: y = MAX - (MAX-i)² / MAX
|
||||
constexpr u16 MAX = 0xFF;
|
||||
u32 d = MAX - i; // (MAX - i)
|
||||
u32 num = d * d + (MAX >> 1); // (MAX-i)² + rounding
|
||||
return u8(MAX - (num / MAX));
|
||||
}
|
||||
|
||||
u8 easeInCubic8(u8 i) {
|
||||
// Simple cubic ease-in: i³ scaled to 8-bit range
|
||||
// y = i³ / MAX²
|
||||
constexpr u16 MAX = 0xFF;
|
||||
constexpr u32 DENOM = (u32)MAX * MAX;
|
||||
constexpr u32 ROUND = DENOM >> 1;
|
||||
|
||||
u32 ii = i;
|
||||
u32 cube = ii * ii * ii; // i³
|
||||
u32 num = cube + ROUND;
|
||||
return u8(num / DENOM);
|
||||
}
|
||||
|
||||
u8 easeOutCubic8(u8 i) {
|
||||
// ease-out cubic: 1 - (1-t)³
|
||||
// For 8-bit: y = MAX - (MAX-i)³ / MAX²
|
||||
constexpr u16 MAX = 0xFF;
|
||||
constexpr u32 DENOM = (u32)MAX * MAX;
|
||||
constexpr u32 ROUND = DENOM >> 1;
|
||||
|
||||
u32 d = MAX - i; // (MAX - i)
|
||||
u32 cube = d * d * d; // (MAX-i)³
|
||||
u32 num = cube + ROUND;
|
||||
return u8(MAX - (num / DENOM));
|
||||
}
|
||||
|
||||
u8 easeInSine8(u8 i) {
|
||||
|
||||
static const u8 easeInSineTable[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4,
|
||||
4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8,
|
||||
8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14,
|
||||
15, 16, 16, 17, 17, 18, 18, 19, 20, 20, 21, 21, 22, 23,
|
||||
23, 24, 25, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33,
|
||||
33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 52, 53, 54, 55, 56, 57,
|
||||
58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72,
|
||||
73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84, 86, 87, 88,
|
||||
89, 90, 91, 93, 94, 95, 96, 98, 99, 100, 101, 103, 104, 105,
|
||||
106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 121, 122, 123,
|
||||
125, 126, 127, 129, 130, 132, 133, 134, 136, 137, 139, 140, 141, 143,
|
||||
144, 146, 147, 148, 150, 151, 153, 154, 156, 157, 159, 160, 161, 163,
|
||||
164, 166, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181, 182, 184,
|
||||
185, 187, 188, 190, 191, 193, 194, 196, 197, 199, 200, 202, 204, 205,
|
||||
207, 208, 210, 211, 213, 214, 216, 217, 219, 221, 222, 224, 225, 227,
|
||||
228, 230, 231, 233, 235, 236, 238, 239, 241, 242, 244, 246, 247, 249,
|
||||
250, 252, 253, 255};
|
||||
|
||||
// ease-in sine: 1 - cos(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
return easeInSineTable[i];
|
||||
}
|
||||
|
||||
u8 easeOutSine8(u8 i) {
|
||||
// ease-out sine: sin(t * π/2)
|
||||
// Delegate to 16-bit version for consistency and accuracy
|
||||
// Scale 8-bit input to 16-bit range, call 16-bit function, scale result back
|
||||
u16 input16 = map8_to_16(i);
|
||||
u16 result16 = easeOutSine16(input16);
|
||||
return map16_to_8(result16);
|
||||
}
|
||||
|
||||
u8 easeInOutSine8(u8 i) {
|
||||
// ease-in-out sine: -(cos(π*t) - 1) / 2
|
||||
// Delegate to 16-bit version for consistency and accuracy
|
||||
// Scale 8-bit input to 16-bit range, call 16-bit function, scale result back
|
||||
u16 input16 = map8_to_16(i);
|
||||
u16 result16 = easeInOutSine16(input16);
|
||||
return map16_to_8(result16);
|
||||
}
|
||||
|
||||
// 16-bit easing functions
|
||||
u16 easeInQuad16(u16 i) {
|
||||
// Simple quadratic ease-in: i^2 scaled to 16-bit range
|
||||
// Using scale16(i, i) which computes (i * i) / 65535
|
||||
return scale16(i, i);
|
||||
}
|
||||
|
||||
u16 easeInOutQuad16(u16 x) {
|
||||
// 16-bit quadratic ease-in / ease-out function
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr u32 HALF = (MAX + 1) >> 1; // 32768
|
||||
constexpr u32 DENOM = MAX; // divisor
|
||||
constexpr u32 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
if (x < HALF) {
|
||||
// first half: y = 2·(x/MAX)² → y_i = 2·x² / MAX
|
||||
fl::u64 xi = x;
|
||||
fl::u64 num = 2 * xi * xi + ROUND; // 2*x², +half for rounding
|
||||
return u16(num / DENOM);
|
||||
} else {
|
||||
// second half: y = 1 − 2·(1−x/MAX)² → y_i = MAX − (2·(MAX−x)² / MAX)
|
||||
fl::u64 d = MAX - x;
|
||||
fl::u64 num = 2 * d * d + ROUND; // 2*(MAX−x)², +half for rounding
|
||||
return u16(MAX - (num / DENOM));
|
||||
}
|
||||
}
|
||||
|
||||
u16 easeInOutCubic16(u16 x) {
|
||||
const u32 MAX = 0xFFFF; // 65535
|
||||
const u32 HALF = (MAX + 1) >> 1; // 32768
|
||||
const fl::u64 M2 = (fl::u64)MAX * MAX; // 65535² = 4 294 836 225
|
||||
|
||||
if (x < HALF) {
|
||||
// first half: y = 4·(x/MAX)³ → y_i = 4·x³ / MAX²
|
||||
fl::u64 xi = x;
|
||||
fl::u64 cube = xi * xi * xi; // x³
|
||||
// add M2/2 for rounding
|
||||
fl::u64 num = 4 * cube + (M2 >> 1);
|
||||
return (u16)(num / M2);
|
||||
} else {
|
||||
// second half: y = 1 − ((2·(1−x/MAX))³)/2
|
||||
// → y_i = MAX − (4·(MAX−x)³ / MAX²)
|
||||
fl::u64 d = MAX - x;
|
||||
fl::u64 cube = d * d * d; // (MAX−x)³
|
||||
fl::u64 num = 4 * cube + (M2 >> 1);
|
||||
return (u16)(MAX - (num / M2));
|
||||
}
|
||||
}
|
||||
|
||||
u16 easeOutQuad16(u16 i) {
|
||||
// ease-out quadratic: 1 - (1-t)²
|
||||
// For 16-bit: y = MAX - (MAX-i)² / MAX
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr u32 ROUND = MAX >> 1; // for rounding
|
||||
|
||||
fl::u64 d = MAX - i; // (MAX - i)
|
||||
fl::u64 num = d * d + ROUND; // (MAX-i)² + rounding
|
||||
return u16(MAX - (num / MAX));
|
||||
}
|
||||
|
||||
u16 easeInCubic16(u16 i) {
|
||||
// Simple cubic ease-in: i³ scaled to 16-bit range
|
||||
// y = i³ / MAX²
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535²
|
||||
constexpr fl::u64 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
fl::u64 ii = i;
|
||||
fl::u64 cube = ii * ii * ii; // i³
|
||||
fl::u64 num = cube + ROUND;
|
||||
return u16(num / DENOM);
|
||||
}
|
||||
|
||||
u16 easeOutCubic16(u16 i) {
|
||||
// ease-out cubic: 1 - (1-t)³
|
||||
// For 16-bit: y = MAX - (MAX-i)³ / MAX²
|
||||
constexpr u32 MAX = 0xFFFF; // 65535
|
||||
constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535²
|
||||
constexpr fl::u64 ROUND = DENOM >> 1; // for rounding
|
||||
|
||||
fl::u64 d = MAX - i; // (MAX - i)
|
||||
fl::u64 cube = d * d * d; // (MAX-i)³
|
||||
fl::u64 num = cube + ROUND;
|
||||
return u16(MAX - (num / DENOM));
|
||||
}
|
||||
|
||||
u16 easeInSine16(u16 i) {
|
||||
// ease-in sine: 1 - cos(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
// Remove the hard-coded boundary for 65535 and let math handle it
|
||||
|
||||
// For 16-bit: use cos32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,4194304] in cos32 space (zero to quarter wave)
|
||||
// Formula: 1 - cos(t * π/2) where t goes from 0 to 1
|
||||
// sin32/cos32 quarter cycle is 16777216/4 = 4194304
|
||||
u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL;
|
||||
i32 cos_result = fl::cos32(angle);
|
||||
|
||||
// Convert cos32 output and apply easing formula: 1 - cos(t * π/2)
|
||||
// cos32 output range is [-2147418112, 2147418112]
|
||||
// At t=0: cos(0) = 2147418112, result should be 0
|
||||
// At t=1: cos(π/2) = 0, result should be 65535
|
||||
|
||||
const fl::i64 MAX_COS32 = 2147418112LL;
|
||||
|
||||
// Calculate: (MAX_COS32 - cos_result) and scale to [0, 65535]
|
||||
fl::i64 adjusted = MAX_COS32 - (fl::i64)cos_result;
|
||||
|
||||
// Scale from [0, 2147418112] to [0, 65535]
|
||||
fl::u64 result = (fl::u64)adjusted * 65535ULL + (MAX_COS32 >> 1); // Add half for rounding
|
||||
u16 final_result = (u16)(result / (fl::u64)MAX_COS32);
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
u16 easeOutSine16(u16 i) {
|
||||
// ease-out sine: sin(t * π/2)
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
if (i == 65535)
|
||||
return 65535;
|
||||
|
||||
// For 16-bit: use sin32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,4194304] in sin32 space (zero to quarter wave)
|
||||
// Formula: sin(t * π/2) where t goes from 0 to 1
|
||||
// sin32 quarter cycle is 16777216/4 = 4194304
|
||||
u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL;
|
||||
i32 sin_result = fl::sin32(angle);
|
||||
|
||||
// Convert sin32 output range [-2147418112, 2147418112] to [0, 65535]
|
||||
// sin32 output is in range -32767*65536 to +32767*65536
|
||||
// For ease-out sine, we only use positive portion [0, 2147418112] -> [0, 65535]
|
||||
return (u16)((fl::u64)sin_result * 65535ULL / 2147418112ULL);
|
||||
}
|
||||
|
||||
u16 easeInOutSine16(u16 i) {
|
||||
// ease-in-out sine: -(cos(π*t) - 1) / 2
|
||||
// Handle boundary conditions explicitly
|
||||
if (i == 0)
|
||||
return 0;
|
||||
if (i == 65535)
|
||||
return 65535;
|
||||
|
||||
// For 16-bit: use cos32 for efficiency and accuracy
|
||||
// Map i from [0,65535] to [0,8388608] in cos32 space (0 to half wave)
|
||||
// Formula: (1 - cos(π*t)) / 2 where t goes from 0 to 1
|
||||
// sin32/cos32 half cycle is 16777216/2 = 8388608
|
||||
u32 angle = ((fl::u64)i * 8388608ULL) / 65535ULL;
|
||||
i32 cos_result = fl::cos32(angle);
|
||||
|
||||
// Convert cos32 output and apply easing formula: (1 - cos(π*t)) / 2
|
||||
// cos32 output range is [-2147418112, 2147418112]
|
||||
// We want: (2147418112 - cos_result) / 2, then scale to [0, 65535]
|
||||
fl::i64 adjusted = (2147418112LL - (fl::i64)cos_result) / 2;
|
||||
return (u16)((fl::u64)adjusted * 65535ULL / 2147418112ULL);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
278
libraries/FastLED/src/fl/ease.h
Normal file
278
libraries/FastLED/src/fl/ease.h
Normal file
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
This are accurate and tested easing functions.
|
||||
|
||||
Note that the easing functions in lib8tion.h are tuned are implemented wrong, such as easeInOutCubic8 and easeInOutCubic16.
|
||||
Modern platforms are so fast that the extra performance is not needed, but accuracy is important.
|
||||
|
||||
*/
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fastled_progmem.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction
|
||||
// Used for converting linear 8-bit values to gamma-corrected 16-bit values
|
||||
extern const u16 gamma_2_8[256] FL_PROGMEM;
|
||||
|
||||
enum EaseType {
|
||||
EASE_NONE,
|
||||
EASE_IN_QUAD,
|
||||
EASE_OUT_QUAD,
|
||||
EASE_IN_OUT_QUAD,
|
||||
EASE_IN_CUBIC,
|
||||
EASE_OUT_CUBIC,
|
||||
EASE_IN_OUT_CUBIC,
|
||||
EASE_IN_SINE,
|
||||
EASE_OUT_SINE,
|
||||
EASE_IN_OUT_SINE,
|
||||
};
|
||||
|
||||
// 8-bit easing functions
|
||||
/// 8-bit quadratic ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts slow and accelerates (ease-in only)
|
||||
u8 easeInQuad8(u8 i);
|
||||
|
||||
/// 8-bit quadratic ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts fast and decelerates (ease-out only)
|
||||
u8 easeOutQuad8(u8 i);
|
||||
|
||||
/// 8-bit quadratic ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// The curve starts slow, accelerates in the middle, then slows down again
|
||||
u8 easeInOutQuad8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced acceleration than quadratic
|
||||
u8 easeInCubic8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced deceleration than quadratic
|
||||
u8 easeOutCubic8(u8 i);
|
||||
|
||||
/// 8-bit cubic ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// More pronounced easing curve than quadratic
|
||||
u8 easeInOutCubic8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-in function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal acceleration
|
||||
u8 easeInSine8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal deceleration
|
||||
u8 easeOutSine8(u8 i);
|
||||
|
||||
/// 8-bit sine ease-in/ease-out function
|
||||
/// Takes an input value 0-255 and returns an eased value 0-255
|
||||
/// Smooth sinusoidal acceleration and deceleration
|
||||
u8 easeInOutSine8(u8 i);
|
||||
|
||||
|
||||
// 16-bit easing functions
|
||||
/// 16-bit quadratic ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInQuad16(u16 i);
|
||||
|
||||
/// 16-bit quadratic ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutQuad16(u16 i);
|
||||
|
||||
/// 16-bit quadratic ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutQuad16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInCubic16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutCubic16(u16 i);
|
||||
|
||||
/// 16-bit cubic ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutCubic16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-in function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInSine16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeOutSine16(u16 i);
|
||||
|
||||
/// 16-bit sine ease-in/ease-out function
|
||||
/// Takes an input value 0-65535 and returns an eased value 0-65535
|
||||
u16 easeInOutSine16(u16 i);
|
||||
|
||||
u16 ease16(EaseType type, u16 i);
|
||||
void ease16(EaseType type, u16* src, u16* dst, u16 count);
|
||||
u8 ease8(EaseType type, u8 i);
|
||||
void ease8(EaseType type, u8* src, u8* dst, u8 count);
|
||||
|
||||
|
||||
//////// INLINE FUNCTIONS ////////
|
||||
|
||||
inline u16 ease16(EaseType type, u16 i) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return i;
|
||||
case EASE_IN_QUAD: return easeInQuad16(i);
|
||||
case EASE_OUT_QUAD: return easeOutQuad16(i);
|
||||
case EASE_IN_OUT_QUAD: return easeInOutQuad16(i);
|
||||
case EASE_IN_CUBIC: return easeInCubic16(i);
|
||||
case EASE_OUT_CUBIC: return easeOutCubic16(i);
|
||||
case EASE_IN_OUT_CUBIC: return easeInOutCubic16(i);
|
||||
case EASE_IN_SINE: return easeInSine16(i);
|
||||
case EASE_OUT_SINE: return easeOutSine16(i);
|
||||
case EASE_IN_OUT_SINE: return easeInOutSine16(i);
|
||||
default: return i;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ease16(EaseType type, u16* src, u16* dst, u16 count) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return;
|
||||
case EASE_IN_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_QUAD: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutQuad16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_CUBIC: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutCubic16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_SINE: {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutSine16(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline u8 ease8(EaseType type, u8 i) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return i;
|
||||
case EASE_IN_QUAD: return easeInQuad8(i);
|
||||
case EASE_OUT_QUAD: return easeOutQuad8(i);
|
||||
case EASE_IN_OUT_QUAD: return easeInOutQuad8(i);
|
||||
case EASE_IN_CUBIC: return easeInCubic8(i);
|
||||
case EASE_OUT_CUBIC: return easeOutCubic8(i);
|
||||
case EASE_IN_OUT_CUBIC: return easeInOutCubic8(i);
|
||||
case EASE_IN_SINE: return easeInSine8(i);
|
||||
case EASE_OUT_SINE: return easeOutSine8(i);
|
||||
case EASE_IN_OUT_SINE: return easeInOutSine8(i);
|
||||
default: return i;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ease8(EaseType type, u8* src, u8* dst, u8 count) {
|
||||
switch (type) {
|
||||
case EASE_NONE: return;
|
||||
case EASE_IN_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_QUAD: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutQuad8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_CUBIC: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutCubic8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_OUT_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeOutSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EASE_IN_OUT_SINE: {
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
dst[i] = easeInOutSine8(src[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
131
libraries/FastLED/src/fl/engine_events.cpp
Normal file
131
libraries/FastLED/src/fl/engine_events.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
|
||||
EngineEvents::Listener::Listener() {}
|
||||
|
||||
EngineEvents::Listener::~Listener() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents *ptr = EngineEvents::getInstance();
|
||||
const bool has_listener = ptr && ptr->_hasListener(this);
|
||||
if (has_listener) {
|
||||
// Warning, the listener should be removed by the subclass. If we are
|
||||
// here then the subclass did not remove the listener and we are now in
|
||||
// a partial state of destruction and the results may be undefined for
|
||||
// multithreaded applications. However, for single threaded (the only
|
||||
// option as of 2024, October) this will be ok.
|
||||
ptr->_removeListener(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents *EngineEvents::getInstance() {
|
||||
return &Singleton<EngineEvents>::instance();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
void EngineEvents::_onPlatformPreLoop() {
|
||||
for (auto &item : mListeners) {
|
||||
auto listener = item.listener;
|
||||
listener->onPlatformPreLoop();
|
||||
}
|
||||
for (auto &item : mListeners) {
|
||||
auto listener = item.listener;
|
||||
listener->onPlatformPreLoop2();
|
||||
}
|
||||
}
|
||||
|
||||
bool EngineEvents::_hasListener(Listener *listener) {
|
||||
auto predicate = [listener](const Pair &pair) {
|
||||
return pair.listener == listener;
|
||||
};
|
||||
return mListeners.find_if(predicate) != mListeners.end();
|
||||
}
|
||||
|
||||
void EngineEvents::_addListener(Listener *listener, int priority) {
|
||||
if (_hasListener(listener)) {
|
||||
return;
|
||||
}
|
||||
for (auto it = mListeners.begin(); it != mListeners.end(); ++it) {
|
||||
if (it->priority < priority) {
|
||||
// this is now the highest priority in this spot.
|
||||
EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
|
||||
mListeners.insert(it, pair);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
|
||||
mListeners.push_back(pair);
|
||||
}
|
||||
|
||||
void EngineEvents::_removeListener(Listener *listener) {
|
||||
auto predicate = [listener](const Pair &pair) {
|
||||
return pair.listener == listener;
|
||||
};
|
||||
auto it = mListeners.find_if(predicate);
|
||||
if (it != mListeners.end()) {
|
||||
mListeners.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onBeginFrame() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onBeginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onEndShowLeds() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onEndShowLeds();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onEndFrame() {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onEndFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onStripAdded(strip, num_leds);
|
||||
}
|
||||
}
|
||||
|
||||
void EngineEvents::_onCanvasUiSet(CLEDController *strip,
|
||||
const ScreenMap &screenmap) {
|
||||
// Make the copy of the listener list to avoid issues with listeners being
|
||||
// added or removed during the loop.
|
||||
ListenerList copy = mListeners;
|
||||
for (auto &item : copy) {
|
||||
auto listener = item.listener;
|
||||
listener->onCanvasUiSet(strip, screenmap);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
|
||||
} // namespace fl
|
||||
150
libraries/FastLED/src/fl/engine_events.h
Normal file
150
libraries/FastLED/src/fl/engine_events.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#ifndef FASTLED_HAS_ENGINE_EVENTS
|
||||
#define FASTLED_HAS_ENGINE_EVENTS SKETCH_HAS_LOTS_OF_MEMORY
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
class CLEDController;
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
class EngineEvents {
|
||||
public:
|
||||
class Listener {
|
||||
public:
|
||||
// Note that the subclass must call EngineEvents::addListener(this) to
|
||||
// start listening. In the subclass destructor, the subclass should call
|
||||
// EngineEvents::removeListener(this).
|
||||
Listener();
|
||||
virtual ~Listener();
|
||||
virtual void onBeginFrame() {}
|
||||
virtual void onEndShowLeds() {}
|
||||
virtual void onEndFrame() {}
|
||||
virtual void onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
(void)strip;
|
||||
(void)num_leds;
|
||||
}
|
||||
// Called to set the canvas for UI elements for a particular strip.
|
||||
virtual void onCanvasUiSet(CLEDController *strip,
|
||||
const ScreenMap &screenmap) {
|
||||
(void)strip;
|
||||
(void)screenmap;
|
||||
}
|
||||
virtual void onPlatformPreLoop() {}
|
||||
virtual void onPlatformPreLoop2() {}
|
||||
};
|
||||
|
||||
static void addListener(Listener *listener, int priority = 0) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_addListener(listener, priority);
|
||||
#else
|
||||
(void)listener;
|
||||
(void)priority;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void removeListener(Listener *listener) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_removeListener(listener);
|
||||
#else
|
||||
(void)listener;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool hasListener(Listener *listener) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
return EngineEvents::getInstance()->_hasListener(listener);
|
||||
#else
|
||||
(void)listener;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onBeginFrame() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onBeginFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onEndShowLeds() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onEndShowLeds();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onEndFrame() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onEndFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onStripAdded(CLEDController *strip, fl::u32 num_leds) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onStripAdded(strip, num_leds);
|
||||
#else
|
||||
(void)strip;
|
||||
(void)num_leds;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap) {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onCanvasUiSet(strip, xymap);
|
||||
#else
|
||||
(void)strip;
|
||||
(void)xymap;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onPlatformPreLoop() {
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
EngineEvents::getInstance()->_onPlatformPreLoop();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Needed by fl::vector<T>
|
||||
EngineEvents() = default;
|
||||
|
||||
private:
|
||||
// Safe to add a listeners during a callback.
|
||||
void _addListener(Listener *listener, int priority);
|
||||
// Safe to remove self during a callback.
|
||||
void _removeListener(Listener *listener);
|
||||
void _onBeginFrame();
|
||||
void _onEndShowLeds();
|
||||
void _onEndFrame();
|
||||
void _onStripAdded(CLEDController *strip, fl::u32 num_leds);
|
||||
void _onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap);
|
||||
void _onPlatformPreLoop();
|
||||
bool _hasListener(Listener *listener);
|
||||
#if FASTLED_HAS_ENGINE_EVENTS
|
||||
struct Pair {
|
||||
Pair() = default;
|
||||
Listener *listener = nullptr;
|
||||
int priority = 0;
|
||||
Pair(Listener *listener, int priority)
|
||||
: listener(listener), priority(priority) {}
|
||||
};
|
||||
|
||||
typedef fl::vector_inlined<Pair, 16> ListenerList;
|
||||
ListenerList mListeners;
|
||||
|
||||
|
||||
static EngineEvents *getInstance();
|
||||
|
||||
friend class fl::Singleton<EngineEvents>;
|
||||
#endif // FASTLED_HAS_ENGINE_EVENTS
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
31
libraries/FastLED/src/fl/eorder.h
Normal file
31
libraries/FastLED/src/fl/eorder.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/// @file fl/eorder.h
|
||||
/// Defines color channel ordering enumerations in the fl namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// RGB color channel orderings, used when instantiating controllers to determine
|
||||
/// what order the controller should send data out in. The default ordering
|
||||
/// is RGB.
|
||||
/// Within this enum, the red channel is 0, the green channel is 1, and the
|
||||
/// blue chanel is 2.
|
||||
enum EOrder {
|
||||
RGB=0012, ///< Red, Green, Blue (0012)
|
||||
RBG=0021, ///< Red, Blue, Green (0021)
|
||||
GRB=0102, ///< Green, Red, Blue (0102)
|
||||
GBR=0120, ///< Green, Blue, Red (0120)
|
||||
BRG=0201, ///< Blue, Red, Green (0201)
|
||||
BGR=0210 ///< Blue, Green, Red (0210)
|
||||
};
|
||||
|
||||
// After EOrder is applied this is where W is inserted for RGBW.
|
||||
enum EOrderW {
|
||||
W3 = 0x3, ///< White is fourth
|
||||
W2 = 0x2, ///< White is third
|
||||
W1 = 0x1, ///< White is second
|
||||
W0 = 0x0, ///< White is first
|
||||
WDefault = W3
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
40
libraries/FastLED/src/fl/export.h
Normal file
40
libraries/FastLED/src/fl/export.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
/// @file export.h
|
||||
/// Cross-platform export macros for FastLED dynamic library support
|
||||
|
||||
#ifndef FASTLED_EXPORT
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
#define FASTLED_EXPORT EMSCRIPTEN_KEEPALIVE
|
||||
#elif defined(_WIN32) || defined(_WIN64)
|
||||
// Windows DLL export/import
|
||||
#ifdef FASTLED_BUILDING_DLL
|
||||
#define FASTLED_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define FASTLED_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined(__GNUC__) && (__GNUC__ >= 4)
|
||||
// GCC/Clang visibility attributes
|
||||
#define FASTLED_EXPORT __attribute__((visibility("default")))
|
||||
#elif defined(__SUNPRO_CC) && (__SUNPRO_CC >= 0x550)
|
||||
// Sun Studio visibility attributes
|
||||
#define FASTLED_EXPORT __global
|
||||
#else
|
||||
// Fallback for other platforms
|
||||
#define FASTLED_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef FASTLED_CALL
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// Windows calling convention
|
||||
#define FASTLED_CALL __stdcall
|
||||
#else
|
||||
// Unix-like platforms - no special calling convention
|
||||
#define FASTLED_CALL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// Combined export and calling convention macro
|
||||
#define FASTLED_API FASTLED_EXPORT FASTLED_CALL
|
||||
333
libraries/FastLED/src/fl/fetch.cpp
Normal file
333
libraries/FastLED/src/fl/fetch.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "fl/fetch.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/mutex.h"
|
||||
#include "fl/singleton.h"
|
||||
#include "fl/engine_events.h"
|
||||
#include "fl/async.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/val.h>
|
||||
#endif
|
||||
|
||||
// Include WASM-specific implementation
|
||||
#include "platforms/wasm/js_fetch.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// ========== WASM Implementation using JavaScript fetch ==========
|
||||
|
||||
|
||||
|
||||
// Promise storage moved to FetchManager singleton
|
||||
|
||||
// Use existing WASM fetch infrastructure
|
||||
void fetch(const fl::string& url, const FetchCallback& callback) {
|
||||
// Use the existing WASM fetch implementation - no conversion needed since both use fl::response
|
||||
wasm_fetch.get(url).response(callback);
|
||||
}
|
||||
|
||||
// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request) {
|
||||
// Create a promise for this request
|
||||
auto promise = fl::promise<response>::create();
|
||||
|
||||
// Register with fetch manager to ensure it's tracked
|
||||
FetchManager::instance().register_promise(promise);
|
||||
|
||||
// Get the actual URL to use (use request URL if provided, otherwise use parameter URL)
|
||||
fl::string fetch_url = request.url().empty() ? url : request.url();
|
||||
|
||||
// Convert our request to the existing WASM fetch system
|
||||
auto wasm_request = WasmFetchRequest(fetch_url);
|
||||
|
||||
// Use lambda that captures the promise directly (shared_ptr is safe to copy)
|
||||
// Make the lambda mutable so we can call non-const methods on the captured promise
|
||||
wasm_request.response([promise](const response& resp) mutable {
|
||||
// Complete the promise directly - no need for double storage
|
||||
if (promise.valid()) {
|
||||
promise.complete_with_value(resp);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else
|
||||
// ========== Embedded/Stub Implementation ==========
|
||||
|
||||
void fetch(const fl::string& url, const FetchCallback& callback) {
|
||||
(void)url; // Unused in stub implementation
|
||||
// For embedded platforms, immediately call callback with a "not supported" response
|
||||
response resp(501, "Not Implemented");
|
||||
resp.set_text("HTTP fetch not supported on this platform");
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request) {
|
||||
(void)request; // Unused in stub implementation
|
||||
FL_WARN("HTTP fetch is not supported on non-WASM platforms. URL: " << url);
|
||||
|
||||
// Create error response
|
||||
response error_response(501, "Not Implemented");
|
||||
error_response.set_body("HTTP fetch is only available in WASM/browser builds. This platform does not support network requests.");
|
||||
|
||||
// Create resolved promise with error response
|
||||
auto promise = fl::promise<response>::resolve(error_response);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// ========== Engine Events Integration ==========
|
||||
|
||||
|
||||
|
||||
// ========== Promise-Based API Implementation ==========
|
||||
|
||||
class FetchEngineListener : public EngineEvents::Listener {
|
||||
public:
|
||||
FetchEngineListener() {
|
||||
EngineEvents::addListener(this);
|
||||
};
|
||||
~FetchEngineListener() override {
|
||||
// Listener base class automatically removes itself
|
||||
EngineEvents::removeListener(this);
|
||||
}
|
||||
|
||||
void onEndFrame() override {
|
||||
// Update all async tasks (fetch, timers, etc.) at the end of each frame
|
||||
fl::async_run();
|
||||
}
|
||||
};
|
||||
|
||||
FetchManager& FetchManager::instance() {
|
||||
return fl::Singleton<FetchManager>::instance();
|
||||
}
|
||||
|
||||
void FetchManager::register_promise(const fl::promise<response>& promise) {
|
||||
// Auto-register with async system and engine listener on first promise
|
||||
if (mActivePromises.empty()) {
|
||||
AsyncManager::instance().register_runner(this);
|
||||
|
||||
if (!mEngineListener) {
|
||||
mEngineListener = fl::make_unique<FetchEngineListener>();
|
||||
EngineEvents::addListener(mEngineListener.get());
|
||||
}
|
||||
}
|
||||
|
||||
mActivePromises.push_back(promise);
|
||||
}
|
||||
|
||||
void FetchManager::update() {
|
||||
// Update all active promises first
|
||||
for (auto& promise : mActivePromises) {
|
||||
if (promise.valid()) {
|
||||
promise.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Then clean up completed/invalid promises in a separate pass
|
||||
cleanup_completed_promises();
|
||||
|
||||
// Auto-unregister from async system when no more promises
|
||||
if (mActivePromises.empty()) {
|
||||
AsyncManager::instance().unregister_runner(this);
|
||||
|
||||
if (mEngineListener) {
|
||||
EngineEvents::removeListener(mEngineListener.get());
|
||||
mEngineListener.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FetchManager::has_active_tasks() const {
|
||||
return !mActivePromises.empty();
|
||||
}
|
||||
|
||||
size_t FetchManager::active_task_count() const {
|
||||
return mActivePromises.size();
|
||||
}
|
||||
|
||||
fl::size FetchManager::active_requests() const {
|
||||
return mActivePromises.size();
|
||||
}
|
||||
|
||||
void FetchManager::cleanup_completed_promises() {
|
||||
// Rebuild vector without completed promises
|
||||
fl::vector<fl::promise<response>> active_promises;
|
||||
for (const auto& promise : mActivePromises) {
|
||||
if (promise.valid() && !promise.is_completed()) {
|
||||
active_promises.push_back(promise);
|
||||
}
|
||||
}
|
||||
mActivePromises = fl::move(active_promises);
|
||||
}
|
||||
|
||||
// WASM promise management methods removed - no longer needed
|
||||
// Promises are now handled directly via shared_ptr capture in callbacks
|
||||
|
||||
// ========== Public API Functions ==========
|
||||
|
||||
fl::promise<response> fetch_get(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with GET method
|
||||
fetch_options get_request(url, RequestOptions("GET"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
get_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
get_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
get_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, get_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_post(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with POST method
|
||||
fetch_options post_request(url, RequestOptions("POST"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
post_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
post_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
post_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, post_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_put(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with PUT method
|
||||
fetch_options put_request(url, RequestOptions("PUT"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
put_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
put_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
put_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, put_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_delete(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with DELETE method
|
||||
fetch_options delete_request(url, RequestOptions("DELETE"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
delete_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
delete_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
delete_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, delete_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_head(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with HEAD method
|
||||
fetch_options head_request(url, RequestOptions("HEAD"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
head_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
head_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
head_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, head_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_http_options(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with OPTIONS method
|
||||
fetch_options options_request(url, RequestOptions("OPTIONS"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
options_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
options_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
options_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, options_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_patch(const fl::string& url, const fetch_options& request) {
|
||||
// Create a new request with PATCH method
|
||||
fetch_options patch_request(url, RequestOptions("PATCH"));
|
||||
|
||||
// Apply any additional options from the provided request
|
||||
const auto& opts = request.options();
|
||||
patch_request.timeout(opts.timeout_ms);
|
||||
for (const auto& header : opts.headers) {
|
||||
patch_request.header(header.first, header.second);
|
||||
}
|
||||
if (!opts.body.empty()) {
|
||||
patch_request.body(opts.body);
|
||||
}
|
||||
|
||||
return execute_fetch_request(url, patch_request);
|
||||
}
|
||||
|
||||
fl::promise<response> fetch_request(const fl::string& url, const RequestOptions& options) {
|
||||
// Create a fetch_options with the provided options
|
||||
fetch_options request(url, options);
|
||||
|
||||
// Use the helper function to execute the request
|
||||
return execute_fetch_request(url, request);
|
||||
}
|
||||
|
||||
void fetch_update() {
|
||||
// Legacy function - use fl::async_run() for new code
|
||||
// This provides backwards compatibility for existing code
|
||||
fl::async_run();
|
||||
}
|
||||
|
||||
fl::size fetch_active_requests() {
|
||||
return FetchManager::instance().active_requests();
|
||||
}
|
||||
|
||||
// ========== Response Class Method Implementations ==========
|
||||
|
||||
fl::Json response::json() const {
|
||||
if (!mJsonParsed) {
|
||||
if (is_json() || mBody.find("{") != fl::string::npos || mBody.find("[") != fl::string::npos) {
|
||||
mCachedJson = parse_json_body();
|
||||
} else {
|
||||
FL_WARN("Response is not JSON: " << mBody);
|
||||
mCachedJson = fl::Json(nullptr); // Not JSON content
|
||||
}
|
||||
mJsonParsed = true;
|
||||
}
|
||||
|
||||
return mCachedJson.has_value() ? *mCachedJson : fl::Json(nullptr);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
326
libraries/FastLED/src/fl/fetch.h
Normal file
326
libraries/FastLED/src/fl/fetch.h
Normal file
@@ -0,0 +1,326 @@
|
||||
#pragma once
|
||||
|
||||
/// @file fetch.h
|
||||
/// @brief Unified HTTP fetch API for FastLED (cross-platform)
|
||||
///
|
||||
/// This API provides both simple callback-based and JavaScript-like promise-based interfaces
|
||||
/// for HTTP requests. Works on WASM/browser platforms with real fetch, provides stubs on embedded.
|
||||
///
|
||||
/// **WASM Optimization:** On WASM platforms, `delay()` automatically pumps all async tasks
|
||||
/// (fetch, timers, etc.) in 1ms intervals, making delay time useful for processing async operations.
|
||||
///
|
||||
/// @section Simple Callback Usage
|
||||
/// @code
|
||||
/// #include "fl/fetch.h"
|
||||
///
|
||||
/// void setup() {
|
||||
/// // Simple callback-based fetch (backward compatible)
|
||||
/// fl::fetch("http://fastled.io", [](const fl::response& resp) {
|
||||
/// if (resp.ok()) {
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// @section Promise Usage
|
||||
/// @code
|
||||
/// #include "fl/fetch.h"
|
||||
///
|
||||
/// void setup() {
|
||||
/// // JavaScript-like fetch with promises
|
||||
/// fl::fetch_get("http://fastled.io")
|
||||
/// .then([](const fl::response& resp) {
|
||||
/// if (resp.ok()) {
|
||||
/// FL_WARN("Success: " << resp.text());
|
||||
/// } else {
|
||||
/// FL_WARN("HTTP Error: " << resp.status() << " " << resp.status_text());
|
||||
/// }
|
||||
/// })
|
||||
/// .catch_([](const fl::Error& err) {
|
||||
/// FL_WARN("Fetch Error: " << err.message);
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// void loop() {
|
||||
/// // Fetch promises are automatically updated through FastLED's engine events!
|
||||
/// // On WASM platforms, delay() also pumps all async tasks automatically.
|
||||
/// // No manual updates needed - just use normal FastLED loop
|
||||
/// FastLED.show();
|
||||
/// delay(16); // delay() automatically pumps all async tasks on WASM
|
||||
/// }
|
||||
/// @endcode
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/promise.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/map.h"
|
||||
#include "fl/hash_map.h"
|
||||
#include "fl/optional.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/async.h"
|
||||
#include "fl/mutex.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/json.h" // Add JSON support for response.json() method
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declarations
|
||||
class fetch_options;
|
||||
class FetchManager;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Forward declarations for WASM-specific types (defined in platforms/wasm/js_fetch.h)
|
||||
class WasmFetchRequest;
|
||||
class WasmFetch;
|
||||
using FetchResponseCallback = fl::function<void(const response&)>;
|
||||
extern WasmFetch wasm_fetch;
|
||||
#endif
|
||||
|
||||
/// HTTP response class (unified interface)
|
||||
class response {
|
||||
public:
|
||||
response() : mStatusCode(200), mStatusText("OK") {}
|
||||
response(int status_code) : mStatusCode(status_code), mStatusText(get_default_status_text(status_code)) {}
|
||||
response(int status_code, const fl::string& status_text)
|
||||
: mStatusCode(status_code), mStatusText(status_text) {}
|
||||
|
||||
/// HTTP status code (like JavaScript response.status)
|
||||
int status() const { return mStatusCode; }
|
||||
|
||||
/// HTTP status text (like JavaScript response.statusText)
|
||||
const fl::string& status_text() const { return mStatusText; }
|
||||
|
||||
/// Check if response is successful (like JavaScript response.ok)
|
||||
bool ok() const { return mStatusCode >= 200 && mStatusCode < 300; }
|
||||
|
||||
/// Response body as text (like JavaScript response.text())
|
||||
const fl::string& text() const { return mBody; }
|
||||
|
||||
/// Get header value (like JavaScript response.headers.get())
|
||||
fl::optional<fl::string> get_header(const fl::string& name) const {
|
||||
auto it = mHeaders.find(name);
|
||||
if (it != mHeaders.end()) {
|
||||
return fl::make_optional(it->second);
|
||||
}
|
||||
return fl::nullopt;
|
||||
}
|
||||
|
||||
/// Get content type convenience method
|
||||
fl::optional<fl::string> get_content_type() const {
|
||||
return get_header("content-type");
|
||||
}
|
||||
|
||||
/// Response body as text (alternative to text())
|
||||
const fl::string& get_body_text() const { return mBody; }
|
||||
|
||||
/// Response body parsed as JSON (JavaScript-like API)
|
||||
/// @return fl::Json object for safe, ergonomic access
|
||||
/// @note Automatically parses JSON on first call, caches result
|
||||
/// @note Returns null JSON object for non-JSON or malformed content
|
||||
fl::Json json() const;
|
||||
|
||||
/// Check if response appears to contain JSON content
|
||||
/// @return true if Content-Type header indicates JSON or body contains JSON markers
|
||||
bool is_json() const {
|
||||
auto content_type = get_content_type();
|
||||
if (content_type.has_value()) {
|
||||
fl::string ct = *content_type;
|
||||
// Check for various JSON content types (case-insensitive)
|
||||
return ct.find("json") != fl::string::npos;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Set methods (internal use)
|
||||
void set_status(int status_code) { mStatusCode = status_code; }
|
||||
void set_status_text(const fl::string& status_text) { mStatusText = status_text; }
|
||||
void set_text(const fl::string& body) { mBody = body; } // Backward compatibility
|
||||
void set_body(const fl::string& body) { mBody = body; }
|
||||
void set_header(const fl::string& name, const fl::string& value) {
|
||||
mHeaders[name] = value;
|
||||
}
|
||||
|
||||
private:
|
||||
int mStatusCode;
|
||||
fl::string mStatusText;
|
||||
fl::string mBody;
|
||||
fl_map<fl::string, fl::string> mHeaders;
|
||||
|
||||
// JSON parsing cache
|
||||
mutable fl::optional<fl::Json> mCachedJson; // Lazy-loaded JSON cache
|
||||
mutable bool mJsonParsed = false; // Track parsing attempts
|
||||
|
||||
/// Parse JSON from response body with error handling
|
||||
fl::Json parse_json_body() const {
|
||||
fl::Json parsed = fl::Json::parse(mBody);
|
||||
if (parsed.is_null() && (!mBody.empty())) {
|
||||
// If parsing failed but we have content, return null JSON
|
||||
// This allows safe chaining: resp.json()["key"] | default
|
||||
return fl::Json(nullptr);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static fl::string get_default_status_text(int status) {
|
||||
switch (status) {
|
||||
case 200: return "OK";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// Callback type for simple fetch responses (backward compatible)
|
||||
using FetchCallback = fl::function<void(const response&)>;
|
||||
|
||||
/// Request options (matches JavaScript fetch RequestInit)
|
||||
struct RequestOptions {
|
||||
fl::string method = "GET";
|
||||
fl_map<fl::string, fl::string> headers;
|
||||
fl::string body;
|
||||
int timeout_ms = 10000; // 10 second default
|
||||
|
||||
RequestOptions() = default;
|
||||
RequestOptions(const fl::string& method_name) : method(method_name) {}
|
||||
};
|
||||
|
||||
/// Fetch options builder (fluent interface)
|
||||
class fetch_options {
|
||||
public:
|
||||
explicit fetch_options(const fl::string& url) : mUrl(url) {}
|
||||
fetch_options(const fl::string& url, const RequestOptions& options)
|
||||
: mUrl(url), mOptions(options) {}
|
||||
|
||||
/// Set HTTP method
|
||||
fetch_options& method(const fl::string& http_method) {
|
||||
mOptions.method = http_method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add header
|
||||
fetch_options& header(const fl::string& name, const fl::string& value) {
|
||||
mOptions.headers[name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set request body
|
||||
fetch_options& body(const fl::string& data) {
|
||||
mOptions.body = data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set JSON body with proper content type
|
||||
fetch_options& json(const fl::string& json_data) {
|
||||
mOptions.body = json_data;
|
||||
mOptions.headers["Content-Type"] = "application/json";
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Set timeout in milliseconds
|
||||
fetch_options& timeout(int timeout_ms) {
|
||||
mOptions.timeout_ms = timeout_ms;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Get the URL for this request
|
||||
const fl::string& url() const { return mUrl; }
|
||||
|
||||
/// Get the options for this request
|
||||
const RequestOptions& options() const { return mOptions; }
|
||||
|
||||
private:
|
||||
fl::string mUrl;
|
||||
RequestOptions mOptions;
|
||||
|
||||
friend class FetchManager;
|
||||
};
|
||||
|
||||
class FetchEngineListener;
|
||||
|
||||
/// Internal fetch manager for promise tracking
|
||||
class FetchManager : public async_runner {
|
||||
public:
|
||||
static FetchManager& instance();
|
||||
|
||||
void register_promise(const fl::promise<response>& promise);
|
||||
|
||||
// async_runner interface
|
||||
void update() override;
|
||||
bool has_active_tasks() const override;
|
||||
size_t active_task_count() const override;
|
||||
|
||||
// Legacy API
|
||||
fl::size active_requests() const;
|
||||
void cleanup_completed_promises();
|
||||
|
||||
private:
|
||||
fl::vector<fl::promise<response>> mActivePromises;
|
||||
fl::unique_ptr<FetchEngineListener> mEngineListener;
|
||||
};
|
||||
|
||||
// ========== Simple Callback API (Backward Compatible) ==========
|
||||
|
||||
/// @brief Make an HTTP GET request (cross-platform, backward compatible)
|
||||
/// @param url The URL to fetch
|
||||
/// @param callback Function to call with the response
|
||||
///
|
||||
/// On WASM/browser platforms: Uses native JavaScript fetch() API
|
||||
/// On Arduino/embedded platforms: Immediately calls callback with error response
|
||||
void fetch(const fl::string& url, const FetchCallback& callback);
|
||||
|
||||
/// @brief Make an HTTP GET request with URL string literal (cross-platform)
|
||||
/// @param url The URL to fetch (C-string)
|
||||
/// @param callback Function to call with the response
|
||||
inline void fetch(const char* url, const FetchCallback& callback) {
|
||||
fetch(fl::string(url), callback);
|
||||
}
|
||||
|
||||
// ========== Promise-Based API (JavaScript-like) ==========
|
||||
|
||||
/// HTTP GET request
|
||||
fl::promise<response> fetch_get(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP POST request
|
||||
fl::promise<response> fetch_post(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP PUT request
|
||||
fl::promise<response> fetch_put(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP DELETE request
|
||||
fl::promise<response> fetch_delete(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP HEAD request
|
||||
fl::promise<response> fetch_head(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP OPTIONS request
|
||||
fl::promise<response> fetch_http_options(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// HTTP PATCH request
|
||||
fl::promise<response> fetch_patch(const fl::string& url, const fetch_options& request = fetch_options(""));
|
||||
|
||||
/// Generic request with options (like fetch(url, options))
|
||||
fl::promise<response> fetch_request(const fl::string& url, const RequestOptions& options = RequestOptions());
|
||||
|
||||
/// Legacy manual update for fetch promises (use fl::async_run() for new code)
|
||||
/// @deprecated Use fl::async_run() instead - this calls async_run() internally
|
||||
void fetch_update();
|
||||
|
||||
/// Get number of active requests
|
||||
fl::size fetch_active_requests();
|
||||
|
||||
/// Internal helper to execute a fetch request and return a promise
|
||||
fl::promise<response> execute_fetch_request(const fl::string& url, const fetch_options& request);
|
||||
|
||||
} // namespace fl
|
||||
74
libraries/FastLED/src/fl/fft.cpp
Normal file
74
libraries/FastLED/src/fl/fft.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/fft_impl.h"
|
||||
#include "fl/hash_map_lru.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/memory.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <> struct Hash<FFT_Args> {
|
||||
fl::u32 operator()(const FFT_Args &key) const noexcept {
|
||||
return MurmurHash3_x86_32(&key, sizeof(FFT_Args));
|
||||
}
|
||||
};
|
||||
|
||||
struct FFT::HashMap : public HashMapLru<FFT_Args, fl::shared_ptr<FFTImpl>> {
|
||||
HashMap(fl::size max_size)
|
||||
: fl::HashMapLru<FFT_Args, fl::shared_ptr<FFTImpl>>(max_size) {}
|
||||
};
|
||||
|
||||
FFT::FFT() { mMap.reset(new HashMap(8)); };
|
||||
|
||||
FFT::~FFT() = default;
|
||||
|
||||
FFT::FFT(const FFT &other) {
|
||||
// copy the map
|
||||
mMap.reset();
|
||||
mMap.reset(new HashMap(*other.mMap));
|
||||
}
|
||||
|
||||
FFT &FFT::operator=(const FFT &other) {
|
||||
mMap.reset();
|
||||
mMap.reset(new HashMap(*other.mMap));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FFT::run(const span<const fl::i16> &sample, FFTBins *out,
|
||||
const FFT_Args &args) {
|
||||
FFT_Args args2 = args;
|
||||
args2.samples = sample.size();
|
||||
get_or_create(args2).run(sample, out);
|
||||
}
|
||||
|
||||
void FFT::clear() { mMap->clear(); }
|
||||
|
||||
fl::size FFT::size() const { return mMap->size(); }
|
||||
|
||||
void FFT::setFFTCacheSize(fl::size size) { mMap->setMaxSize(size); }
|
||||
|
||||
FFTImpl &FFT::get_or_create(const FFT_Args &args) {
|
||||
fl::shared_ptr<FFTImpl> *val = mMap->find_value(args);
|
||||
if (val) {
|
||||
// we have it.
|
||||
return **val;
|
||||
}
|
||||
// else we have to make a new one.
|
||||
fl::shared_ptr<FFTImpl> fft = fl::make_shared<FFTImpl>(args);
|
||||
(*mMap)[args] = fft;
|
||||
return *fft;
|
||||
}
|
||||
|
||||
bool FFT_Args::operator==(const FFT_Args &other) const {
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(float-equal);
|
||||
|
||||
return samples == other.samples && bands == other.bands &&
|
||||
fmin == other.fmin && fmax == other.fmax &&
|
||||
sample_rate == other.sample_rate;
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
125
libraries/FastLED/src/fl/fft.h
Normal file
125
libraries/FastLED/src/fl/fft.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/move.h"
|
||||
#include "fl/memfill.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class FFTImpl;
|
||||
class AudioSample;
|
||||
|
||||
struct FFTBins {
|
||||
public:
|
||||
FFTBins(fl::size n) : mSize(n) {
|
||||
bins_raw.reserve(n);
|
||||
bins_db.reserve(n);
|
||||
}
|
||||
|
||||
// Copy constructor and assignment
|
||||
FFTBins(const FFTBins &other) : bins_raw(other.bins_raw), bins_db(other.bins_db), mSize(other.mSize) {}
|
||||
FFTBins &operator=(const FFTBins &other) {
|
||||
if (this != &other) {
|
||||
mSize = other.mSize;
|
||||
bins_raw = other.bins_raw;
|
||||
bins_db = other.bins_db;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move constructor and assignment
|
||||
FFTBins(FFTBins &&other) noexcept
|
||||
: bins_raw(fl::move(other.bins_raw)), bins_db(fl::move(other.bins_db)), mSize(other.mSize) {}
|
||||
|
||||
FFTBins &operator=(FFTBins &&other) noexcept {
|
||||
if (this != &other) {
|
||||
bins_raw = fl::move(other.bins_raw);
|
||||
bins_db = fl::move(other.bins_db);
|
||||
mSize = other.mSize;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
bins_raw.clear();
|
||||
bins_db.clear();
|
||||
}
|
||||
|
||||
fl::size size() const { return mSize; }
|
||||
|
||||
// The bins are the output of the FFTImpl.
|
||||
fl::vector<float> bins_raw;
|
||||
// The frequency range of the bins.
|
||||
fl::vector<float> bins_db;
|
||||
|
||||
private:
|
||||
fl::size mSize;
|
||||
};
|
||||
|
||||
struct FFT_Args {
|
||||
static int DefaultSamples() { return 512; }
|
||||
static int DefaultBands() { return 16; }
|
||||
static float DefaultMinFrequency() { return 174.6f; }
|
||||
static float DefaultMaxFrequency() { return 4698.3f; }
|
||||
static int DefaultSampleRate() { return 44100; }
|
||||
|
||||
int samples;
|
||||
int bands;
|
||||
float fmin;
|
||||
float fmax;
|
||||
int sample_rate;
|
||||
|
||||
FFT_Args(int samples = DefaultSamples(), int bands = DefaultBands(),
|
||||
float fmin = DefaultMinFrequency(),
|
||||
float fmax = DefaultMaxFrequency(),
|
||||
int sample_rate = DefaultSampleRate()) {
|
||||
// Memset so that this object can be hashed without garbage from packed
|
||||
// in data.
|
||||
fl::memfill(this, 0, sizeof(FFT_Args));
|
||||
this->samples = samples;
|
||||
this->bands = bands;
|
||||
this->fmin = fmin;
|
||||
this->fmax = fmax;
|
||||
this->sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
// Rule of 5 for POD data
|
||||
FFT_Args(const FFT_Args &other) = default;
|
||||
FFT_Args &operator=(const FFT_Args &other) = default;
|
||||
FFT_Args(FFT_Args &&other) noexcept = default;
|
||||
FFT_Args &operator=(FFT_Args &&other) noexcept = default;
|
||||
|
||||
bool operator==(const FFT_Args &other) const ;
|
||||
bool operator!=(const FFT_Args &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
class FFT {
|
||||
public:
|
||||
FFT();
|
||||
~FFT();
|
||||
|
||||
FFT(FFT &&) = default;
|
||||
FFT &operator=(FFT &&) = default;
|
||||
FFT(const FFT & other);
|
||||
FFT &operator=(const FFT & other);
|
||||
|
||||
void run(const span<const i16> &sample, FFTBins *out,
|
||||
const FFT_Args &args = FFT_Args());
|
||||
|
||||
void clear();
|
||||
fl::size size() const;
|
||||
|
||||
// FFT's are expensive to create, so we cache them. This sets the size of
|
||||
// the cache. The default is 8.
|
||||
void setFFTCacheSize(fl::size size);
|
||||
|
||||
private:
|
||||
// Get the FFTImpl for the given arguments.
|
||||
FFTImpl &get_or_create(const FFT_Args &args);
|
||||
struct HashMap;
|
||||
scoped_ptr<HashMap> mMap;
|
||||
};
|
||||
|
||||
}; // namespace fl
|
||||
171
libraries/FastLED/src/fl/fft_impl.cpp
Normal file
171
libraries/FastLED/src/fl/fft_impl.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/// #include <Arduino.h>
|
||||
// #include <iostream>
|
||||
// #include "audio_types.h"
|
||||
// // #include "defs.h"
|
||||
// #include "thirdparty/cq_kernel/cq_kernel.h"
|
||||
// #include "thirdparty/cq_kernel/kiss_fftr.h"
|
||||
// #include "util.h"
|
||||
|
||||
#ifndef FASTLED_INTERNAL
|
||||
#define FASTLED_INTERNAL 1
|
||||
#endif
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "third_party/cq_kernel/cq_kernel.h"
|
||||
#include "third_party/cq_kernel/kiss_fftr.h"
|
||||
|
||||
#include "fl/array.h"
|
||||
#include "fl/audio.h"
|
||||
#include "fl/fft.h"
|
||||
#include "fl/fft_impl.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
#include "fl/memfill.h"
|
||||
|
||||
#define AUDIO_SAMPLE_RATE 44100
|
||||
#define SAMPLES 512
|
||||
#define BANDS 16
|
||||
#define SAMPLING_FREQUENCY AUDIO_SAMPLE_RATE
|
||||
#define MAX_FREQUENCY 4698.3
|
||||
#define MIN_FREQUENCY 174.6
|
||||
#define MIN_VAL 5000 // Equivalent to 0.15 in Q15
|
||||
|
||||
#define PRINT_HEADER 1
|
||||
|
||||
namespace fl {
|
||||
|
||||
class FFTContext {
|
||||
public:
|
||||
FFTContext(int samples, int bands, float fmin, float fmax, int sample_rate)
|
||||
: m_fftr_cfg(nullptr), m_kernels(nullptr) {
|
||||
fl::memfill(&m_cq_cfg, 0, sizeof(m_cq_cfg));
|
||||
m_cq_cfg.samples = samples;
|
||||
m_cq_cfg.bands = bands;
|
||||
m_cq_cfg.fmin = fmin;
|
||||
m_cq_cfg.fmax = fmax;
|
||||
m_cq_cfg.fs = sample_rate;
|
||||
m_cq_cfg.min_val = MIN_VAL;
|
||||
m_fftr_cfg = kiss_fftr_alloc(samples, 0, NULL, NULL);
|
||||
if (!m_fftr_cfg) {
|
||||
FASTLED_WARN("Failed to allocate FFTImpl context");
|
||||
return;
|
||||
}
|
||||
m_kernels = generate_kernels(m_cq_cfg);
|
||||
}
|
||||
~FFTContext() {
|
||||
if (m_fftr_cfg) {
|
||||
kiss_fftr_free(m_fftr_cfg);
|
||||
}
|
||||
if (m_kernels) {
|
||||
free_kernels(m_kernels, m_cq_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
fl::size sampleSize() const { return m_cq_cfg.samples; }
|
||||
|
||||
void fft_unit_test(span<const i16> buffer, FFTBins *out) {
|
||||
|
||||
// FASTLED_ASSERT(512 == m_cq_cfg.samples, "FFTImpl samples mismatch and
|
||||
// are still hardcoded to 512");
|
||||
out->clear();
|
||||
// allocate
|
||||
FASTLED_STACK_ARRAY(kiss_fft_cpx, fft, m_cq_cfg.samples);
|
||||
FASTLED_STACK_ARRAY(kiss_fft_cpx, cq, m_cq_cfg.bands);
|
||||
// initialize
|
||||
kiss_fftr(m_fftr_cfg, buffer.data(), fft);
|
||||
apply_kernels(fft, cq, m_kernels, m_cq_cfg);
|
||||
const float maxf = m_cq_cfg.fmax;
|
||||
const float minf = m_cq_cfg.fmin;
|
||||
const float delta_f = (maxf - minf) / m_cq_cfg.bands;
|
||||
// begin transform
|
||||
for (int i = 0; i < m_cq_cfg.bands; ++i) {
|
||||
i32 real = cq[i].r;
|
||||
i32 imag = cq[i].i;
|
||||
float r2 = float(real * real);
|
||||
float i2 = float(imag * imag);
|
||||
float magnitude = sqrt(r2 + i2);
|
||||
float magnitude_db = 20 * log10(magnitude);
|
||||
float f_start = minf + i * delta_f;
|
||||
float f_end = f_start + delta_f;
|
||||
FASTLED_UNUSED(f_start);
|
||||
FASTLED_UNUSED(f_end);
|
||||
|
||||
if (magnitude <= 0.0f) {
|
||||
magnitude_db = 0.0f;
|
||||
}
|
||||
|
||||
// FASTLED_UNUSED(magnitude_db);
|
||||
// FASTLED_WARN("magnitude_db: " << magnitude_db);
|
||||
// out->push_back(magnitude_db);
|
||||
out->bins_raw.push_back(magnitude);
|
||||
out->bins_db.push_back(magnitude_db);
|
||||
}
|
||||
}
|
||||
|
||||
fl::string info() const {
|
||||
// Calculate frequency delta
|
||||
float delta_f = (m_cq_cfg.fmax - m_cq_cfg.fmin) / m_cq_cfg.bands;
|
||||
fl::StrStream ss;
|
||||
ss << "FFTImpl Frequency Bands: ";
|
||||
|
||||
for (int i = 0; i < m_cq_cfg.bands; ++i) {
|
||||
float f_start = m_cq_cfg.fmin + i * delta_f;
|
||||
float f_end = f_start + delta_f;
|
||||
ss << f_start << "Hz-" << f_end << "Hz, ";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
kiss_fftr_cfg m_fftr_cfg;
|
||||
cq_kernels_t m_kernels;
|
||||
cq_kernel_cfg m_cq_cfg;
|
||||
};
|
||||
|
||||
FFTImpl::FFTImpl(const FFT_Args &args) {
|
||||
mContext.reset(new FFTContext(args.samples, args.bands, args.fmin,
|
||||
args.fmax, args.sample_rate));
|
||||
}
|
||||
|
||||
FFTImpl::~FFTImpl() { mContext.reset(); }
|
||||
|
||||
fl::string FFTImpl::info() const {
|
||||
if (mContext) {
|
||||
return mContext->info();
|
||||
} else {
|
||||
FASTLED_WARN("FFTImpl context is not initialized");
|
||||
return fl::string();
|
||||
}
|
||||
}
|
||||
|
||||
fl::size FFTImpl::sampleSize() const {
|
||||
if (mContext) {
|
||||
return mContext->sampleSize();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FFTImpl::Result FFTImpl::run(const AudioSample &sample, FFTBins *out) {
|
||||
auto &audio_sample = sample.pcm();
|
||||
span<const i16> slice(audio_sample);
|
||||
return run(slice, out);
|
||||
}
|
||||
|
||||
FFTImpl::Result FFTImpl::run(span<const i16> sample, FFTBins *out) {
|
||||
if (!mContext) {
|
||||
return FFTImpl::Result(false, "FFTImpl context is not initialized");
|
||||
}
|
||||
if (sample.size() != mContext->sampleSize()) {
|
||||
FASTLED_WARN("FFTImpl sample size mismatch");
|
||||
return FFTImpl::Result(false, "FFTImpl sample size mismatch");
|
||||
}
|
||||
mContext->fft_unit_test(sample, out);
|
||||
return FFTImpl::Result(true, "");
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
58
libraries/FastLED/src/fl/fft_impl.h
Normal file
58
libraries/FastLED/src/fl/fft_impl.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/hash_map_lru.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/unique_ptr.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class AudioSample;
|
||||
class FFTContext;
|
||||
struct FFT_Args;
|
||||
|
||||
// Example:
|
||||
// FFTImpl fft(512, 16);
|
||||
// auto sample = SINE WAVE OF 512 SAMPLES
|
||||
// fft.run(buffer, &out);
|
||||
// FASTLED_WARN("FFTImpl output: " << out); // 16 bands of output.
|
||||
class FFTImpl {
|
||||
public:
|
||||
// Result indicating success or failure of the FFTImpl run (in which case
|
||||
// there will be an error message).
|
||||
struct Result {
|
||||
Result(bool ok, const string &error) : ok(ok), error(error) {}
|
||||
bool ok = false;
|
||||
fl::string error;
|
||||
};
|
||||
// Default values for the FFTImpl.
|
||||
FFTImpl(const FFT_Args &args);
|
||||
~FFTImpl();
|
||||
|
||||
fl::size sampleSize() const;
|
||||
// Note that the sample sizes MUST match the samples size passed into the
|
||||
// constructor.
|
||||
Result run(const AudioSample &sample, FFTBins *out);
|
||||
Result run(span<const i16> sample, FFTBins *out);
|
||||
// Info on what the frequency the bins represent
|
||||
fl::string info() const;
|
||||
|
||||
// Detail.
|
||||
static int DefaultSamples() { return 512; }
|
||||
static int DefaultBands() { return 16; }
|
||||
static float DefaultMinFrequency() { return 174.6f; }
|
||||
static float DefaultMaxFrequency() { return 4698.3f; }
|
||||
static int DefaultSampleRate() { return 44100; }
|
||||
|
||||
// Disable copy and move constructors and assignment operators
|
||||
FFTImpl(const FFTImpl &) = delete;
|
||||
FFTImpl &operator=(const FFTImpl &) = delete;
|
||||
FFTImpl(FFTImpl &&) = delete;
|
||||
FFTImpl &operator=(FFTImpl &&) = delete;
|
||||
|
||||
private:
|
||||
fl::unique_ptr<FFTContext> mContext;
|
||||
};
|
||||
|
||||
}; // namespace fl
|
||||
206
libraries/FastLED/src/fl/file_system.cpp
Normal file
206
libraries/FastLED/src/fl/file_system.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "fl/file_system.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/has_include.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "platforms/wasm/fs_wasm.h"
|
||||
#define FASTLED_HAS_SDCARD 1
|
||||
#elif FL_HAS_INCLUDE(<SD.h>) && FL_HAS_INCLUDE(<fs.h>)
|
||||
// Include Arduino SD card implementation when SD library is available
|
||||
#include "platforms/fs_sdcard_arduino.hpp"
|
||||
#define FASTLED_HAS_SDCARD 1
|
||||
#else
|
||||
#define FASTLED_HAS_SDCARD 0
|
||||
#endif
|
||||
|
||||
#include "fl/json.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/unused.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class NullFileHandle : public FileHandle {
|
||||
public:
|
||||
NullFileHandle() = default;
|
||||
~NullFileHandle() override {}
|
||||
|
||||
bool available() const override { return false; }
|
||||
fl::size size() const override { return 0; }
|
||||
fl::size read(u8 *dst, fl::size bytesToRead) override {
|
||||
FASTLED_UNUSED(dst);
|
||||
FASTLED_UNUSED(bytesToRead);
|
||||
return 0;
|
||||
}
|
||||
fl::size pos() const override { return 0; }
|
||||
const char *path() const override { return "NULL FILE HANDLE"; }
|
||||
bool seek(fl::size pos) override {
|
||||
FASTLED_UNUSED(pos);
|
||||
return false;
|
||||
}
|
||||
void close() override {}
|
||||
bool valid() const override {
|
||||
FASTLED_WARN("NullFileHandle is not valid");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class NullFileSystem : public FsImpl {
|
||||
public:
|
||||
NullFileSystem() {
|
||||
FASTLED_WARN("NullFileSystem instantiated as a placeholder, please "
|
||||
"implement a file system for your platform.");
|
||||
}
|
||||
~NullFileSystem() override {}
|
||||
|
||||
bool begin() override { return true; }
|
||||
void end() override {}
|
||||
|
||||
void close(FileHandlePtr file) override {
|
||||
// No need to do anything for in-memory files
|
||||
FASTLED_UNUSED(file);
|
||||
FASTLED_WARN("NullFileSystem::close");
|
||||
}
|
||||
|
||||
FileHandlePtr openRead(const char *_path) override {
|
||||
FASTLED_UNUSED(_path);
|
||||
fl::shared_ptr<NullFileHandle> ptr = fl::make_shared<NullFileHandle>();
|
||||
FileHandlePtr out = ptr;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
bool FileSystem::beginSd(int cs_pin) {
|
||||
mFs = make_sdcard_filesystem(cs_pin);
|
||||
if (!mFs) {
|
||||
return false;
|
||||
}
|
||||
mFs->begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::begin(FsImplPtr platform_filesystem) {
|
||||
mFs = platform_filesystem;
|
||||
if (!mFs) {
|
||||
return false;
|
||||
}
|
||||
mFs->begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
fl::size FileHandle::bytesLeft() const { return size() - pos(); }
|
||||
|
||||
FileSystem::FileSystem() : mFs() {}
|
||||
|
||||
void FileSystem::end() {
|
||||
if (mFs) {
|
||||
mFs->end();
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSystem::readJson(const char *path, Json *doc) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse using the new Json class
|
||||
*doc = fl::Json::parse(text);
|
||||
return !doc->is_null();
|
||||
}
|
||||
|
||||
bool FileSystem::readScreenMaps(const char *path,
|
||||
fl::fl_map<string, ScreenMap> *out, string *error) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
FASTLED_WARN("Failed to read file: " << path);
|
||||
if (error) {
|
||||
*error = "Failed to read file: ";
|
||||
error->append(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
string err;
|
||||
bool ok = ScreenMap::ParseJson(text.c_str(), out, &err);
|
||||
if (!ok) {
|
||||
FASTLED_WARN("Failed to parse screen map: " << err.c_str());
|
||||
*error = err;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::readScreenMap(const char *path, const char *name,
|
||||
ScreenMap *out, string *error) {
|
||||
string text;
|
||||
if (!readText(path, &text)) {
|
||||
FASTLED_WARN("Failed to read file: " << path);
|
||||
if (error) {
|
||||
*error = "Failed to read file: ";
|
||||
error->append(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
string err;
|
||||
bool ok = ScreenMap::ParseJson(text.c_str(), name, out, &err);
|
||||
if (!ok) {
|
||||
FASTLED_WARN("Failed to parse screen map: " << err.c_str());
|
||||
*error = err;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSystem::close(FileHandlePtr file) { mFs->close(file); }
|
||||
|
||||
FileHandlePtr FileSystem::openRead(const char *path) {
|
||||
return mFs->openRead(path);
|
||||
}
|
||||
Video FileSystem::openVideo(const char *path, fl::size pixelsPerFrame, float fps,
|
||||
fl::size nFrameHistory) {
|
||||
Video video(pixelsPerFrame, fps, nFrameHistory);
|
||||
FileHandlePtr file = openRead(path);
|
||||
if (!file) {
|
||||
video.setError(fl::string("Could not open file: ").append(path));
|
||||
return video;
|
||||
}
|
||||
video.begin(file);
|
||||
return video;
|
||||
}
|
||||
|
||||
bool FileSystem::readText(const char *path, fl::string *out) {
|
||||
FileHandlePtr file = openRead(path);
|
||||
if (!file) {
|
||||
FASTLED_WARN("Failed to open file: " << path);
|
||||
return false;
|
||||
}
|
||||
fl::size size = file->size();
|
||||
out->reserve(size + out->size());
|
||||
bool wrote = false;
|
||||
while (file->available()) {
|
||||
u8 buf[64];
|
||||
fl::size n = file->read(buf, sizeof(buf));
|
||||
// out->append(buf, n);
|
||||
out->append((const char *)buf, n);
|
||||
wrote = true;
|
||||
}
|
||||
file->close();
|
||||
FASTLED_DBG_IF(!wrote, "Failed to write any data to the output string.");
|
||||
return wrote;
|
||||
}
|
||||
} // namespace fl
|
||||
|
||||
namespace fl {
|
||||
#if !FASTLED_HAS_SDCARD
|
||||
// Weak fallback implementation when SD library is not available
|
||||
FL_LINK_WEAK FsImplPtr make_sdcard_filesystem(int cs_pin) {
|
||||
FASTLED_UNUSED(cs_pin);
|
||||
fl::shared_ptr<NullFileSystem> ptr = fl::make_shared<NullFileSystem>();
|
||||
FsImplPtr out = ptr;
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace fl
|
||||
108
libraries/FastLED/src/fl/file_system.h
Normal file
108
libraries/FastLED/src/fl/file_system.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
// Note, fs.h breaks ESPAsyncWebServer so we use file_system.h instead.
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/memory.h"
|
||||
#include "fl/str.h"
|
||||
#include "fx/video.h"
|
||||
#include "fl/screenmap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
FASTLED_SMART_PTR(FsImpl);
|
||||
// PLATFORM INTERFACE
|
||||
// You need to define this for your platform.
|
||||
// Otherwise a null filesystem will be used that will do nothing but spew
|
||||
// warnings, but otherwise won't crash the system.
|
||||
FsImplPtr make_sdcard_filesystem(int cs_pin);
|
||||
} // namespace fl
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
struct CRGB;
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
namespace fl {
|
||||
|
||||
class ScreenMap;
|
||||
FASTLED_SMART_PTR(FileSystem);
|
||||
FASTLED_SMART_PTR(FileHandle);
|
||||
class Video;
|
||||
template <typename Key, typename Value, fl::size N> class FixedMap;
|
||||
|
||||
namespace json2 {
|
||||
class Json;
|
||||
}
|
||||
|
||||
class FileSystem {
|
||||
public:
|
||||
FileSystem();
|
||||
bool beginSd(int cs_pin); // Signal to begin using the filesystem resource.
|
||||
bool begin(FsImplPtr platform_filesystem); // Signal to begin using the
|
||||
// filesystem resource.
|
||||
void end(); // Signal to end use of the file system.
|
||||
|
||||
FileHandlePtr
|
||||
openRead(const char *path); // Null if file could not be opened.
|
||||
Video
|
||||
openVideo(const char *path, fl::size pixelsPerFrame, float fps = 30.0f,
|
||||
fl::size nFrameHistory = 0); // Null if video could not be opened.
|
||||
bool readText(const char *path, string *out);
|
||||
bool readJson(const char *path, Json *doc);
|
||||
bool readScreenMaps(const char *path, fl::fl_map<string, ScreenMap> *out,
|
||||
string *error = nullptr);
|
||||
bool readScreenMap(const char *path, const char *name, ScreenMap *out,
|
||||
string *error = nullptr);
|
||||
void close(FileHandlePtr file);
|
||||
|
||||
private:
|
||||
FsImplPtr mFs; // System dependent filesystem.
|
||||
};
|
||||
|
||||
// An abstract class that represents a file handle.
|
||||
// Devices like the SD card will return one of these.
|
||||
class FileHandle {
|
||||
public:
|
||||
virtual ~FileHandle() {}
|
||||
virtual bool available() const = 0;
|
||||
virtual fl::size bytesLeft() const;
|
||||
virtual fl::size size() const = 0;
|
||||
virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0;
|
||||
virtual fl::size pos() const = 0;
|
||||
virtual const char *path() const = 0;
|
||||
virtual bool seek(fl::size pos) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual bool valid() const = 0;
|
||||
|
||||
// convenience functions
|
||||
fl::size readCRGB(CRGB *dst, fl::size n) {
|
||||
return read((fl::u8 *)dst, n * 3) / 3;
|
||||
}
|
||||
};
|
||||
|
||||
// Platforms will subclass this to implement the filesystem.
|
||||
class FsImpl {
|
||||
public:
|
||||
struct Visitor {
|
||||
virtual ~Visitor() {}
|
||||
virtual void accept(const char *path) = 0;
|
||||
};
|
||||
FsImpl() = default;
|
||||
virtual ~FsImpl() {} // Use default pins for spi.
|
||||
virtual bool begin() = 0;
|
||||
// End use of card
|
||||
virtual void end() = 0;
|
||||
virtual void close(FileHandlePtr file) = 0;
|
||||
virtual FileHandlePtr openRead(const char *path) = 0;
|
||||
|
||||
virtual bool ls(Visitor &visitor) {
|
||||
// todo: implement.
|
||||
(void)visitor;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
173
libraries/FastLED/src/fl/fill.cpp
Normal file
173
libraries/FastLED/src/fl/fill.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fill.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
void fill_solid(struct CRGB *targetArray, int numToFill,
|
||||
const struct CRGB &color) {
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_solid(struct CHSV *targetArray, int numToFill,
|
||||
const struct CHSV &color) {
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
// void fill_solid( struct CRGB* targetArray, int numToFill,
|
||||
// const struct CHSV& hsvColor)
|
||||
// {
|
||||
// fill_solid<CRGB>( targetArray, numToFill, (CRGB) hsvColor);
|
||||
// }
|
||||
|
||||
void fill_rainbow(struct CRGB *targetArray, int numToFill, u8 initialhue,
|
||||
u8 deltahue) {
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
hsv.hue += deltahue;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow(struct CHSV *targetArray, int numToFill, u8 initialhue,
|
||||
u8 deltahue) {
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
hsv.hue += deltahue;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow_circular(struct CRGB *targetArray, int numToFill,
|
||||
u8 initialhue, bool reversed) {
|
||||
if (numToFill == 0)
|
||||
return; // avoiding div/0
|
||||
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
|
||||
const u16 hueChange =
|
||||
65535 / (u16)numToFill; // hue change for each LED, * 256 for
|
||||
// precision (256 * 256 - 1)
|
||||
u16 hueOffset = 0; // offset for hue value, with precision (*256)
|
||||
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
if (reversed)
|
||||
hueOffset -= hueChange;
|
||||
else
|
||||
hueOffset += hueChange;
|
||||
hsv.hue = initialhue +
|
||||
(u8)(hueOffset >>
|
||||
8); // assign new hue with precise offset (as 8-bit)
|
||||
}
|
||||
}
|
||||
|
||||
void fill_rainbow_circular(struct CHSV *targetArray, int numToFill,
|
||||
u8 initialhue, bool reversed) {
|
||||
if (numToFill == 0)
|
||||
return; // avoiding div/0
|
||||
|
||||
CHSV hsv;
|
||||
hsv.hue = initialhue;
|
||||
hsv.val = 255;
|
||||
hsv.sat = 240;
|
||||
|
||||
const u16 hueChange =
|
||||
65535 / (u16)numToFill; // hue change for each LED, * 256 for
|
||||
// precision (256 * 256 - 1)
|
||||
u16 hueOffset = 0; // offset for hue value, with precision (*256)
|
||||
|
||||
for (int i = 0; i < numToFill; ++i) {
|
||||
targetArray[i] = hsv;
|
||||
if (reversed)
|
||||
hueOffset -= hueChange;
|
||||
else
|
||||
hueOffset += hueChange;
|
||||
hsv.hue = initialhue +
|
||||
(u8)(hueOffset >>
|
||||
8); // assign new hue with precise offset (as 8-bit)
|
||||
}
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor,
|
||||
u16 endpos, CRGB endcolor) {
|
||||
// if the points are in the wrong order, straighten them
|
||||
if (endpos < startpos) {
|
||||
u16 t = endpos;
|
||||
CRGB tc = endcolor;
|
||||
endcolor = startcolor;
|
||||
endpos = startpos;
|
||||
startpos = t;
|
||||
startcolor = tc;
|
||||
}
|
||||
|
||||
saccum87 rdistance87;
|
||||
saccum87 gdistance87;
|
||||
saccum87 bdistance87;
|
||||
|
||||
rdistance87 = (endcolor.r - startcolor.r) << 7;
|
||||
gdistance87 = (endcolor.g - startcolor.g) << 7;
|
||||
bdistance87 = (endcolor.b - startcolor.b) << 7;
|
||||
|
||||
u16 pixeldistance = endpos - startpos;
|
||||
i16 divisor = pixeldistance ? pixeldistance : 1;
|
||||
|
||||
saccum87 rdelta87 = rdistance87 / divisor;
|
||||
saccum87 gdelta87 = gdistance87 / divisor;
|
||||
saccum87 bdelta87 = bdistance87 / divisor;
|
||||
|
||||
rdelta87 *= 2;
|
||||
gdelta87 *= 2;
|
||||
bdelta87 *= 2;
|
||||
|
||||
accum88 r88 = startcolor.r << 8;
|
||||
accum88 g88 = startcolor.g << 8;
|
||||
accum88 b88 = startcolor.b << 8;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
leds[i] = CRGB(r88 >> 8, g88 >> 8, b88 >> 8);
|
||||
r88 += rdelta87;
|
||||
g88 += gdelta87;
|
||||
b88 += bdelta87;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2) {
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, last, c2);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3) {
|
||||
u16 half = (numLeds / 2);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, half, c2);
|
||||
fill_gradient_RGB(leds, half, c2, last, c3);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3, const CRGB &c4) {
|
||||
u16 onethird = (numLeds / 3);
|
||||
u16 twothirds = ((numLeds * 2) / 3);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient_RGB(leds, 0, c1, onethird, c2);
|
||||
fill_gradient_RGB(leds, onethird, c2, twothirds, c3);
|
||||
fill_gradient_RGB(leds, twothirds, c3, last, c4);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
291
libraries/FastLED/src/fl/fill.h
Normal file
291
libraries/FastLED/src/fl/fill.h
Normal file
@@ -0,0 +1,291 @@
|
||||
#pragma once
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/colorutils_misc.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_UNUSED_PARAMETER
|
||||
FL_DISABLE_WARNING_RETURN_TYPE
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
|
||||
/// ANSI: signed short _Accum. 8 bits int, 7 bits fraction
|
||||
/// @see accum88
|
||||
#define saccum87 i16
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @defgroup ColorUtils Color Utility Functions
|
||||
/// A variety of functions for working with color, palettes, and leds
|
||||
/// @{
|
||||
|
||||
/// @defgroup ColorFills Color Fill Functions
|
||||
/// Functions for filling LED arrays with colors and gradients
|
||||
/// @{
|
||||
|
||||
/// Fill a range of LEDs with a solid color.
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param color the color to fill with
|
||||
void fill_solid(struct CRGB *targetArray, int numToFill,
|
||||
const struct CRGB &color);
|
||||
|
||||
/// @copydoc fill_solid()
|
||||
void fill_solid(struct CHSV *targetArray, int numToFill,
|
||||
const struct CHSV &color);
|
||||
|
||||
/// Fill a range of LEDs with a rainbow of colors.
|
||||
/// The colors making up the rainbow are at full saturation and full
|
||||
/// value (brightness).
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param initialhue the starting hue for the rainbow
|
||||
/// @param deltahue how many hue values to advance for each LED
|
||||
void fill_rainbow(struct CRGB *targetArray, int numToFill, fl::u8 initialhue,
|
||||
fl::u8 deltahue = 5);
|
||||
|
||||
/// @copydoc fill_rainbow()
|
||||
void fill_rainbow(struct CHSV *targetArray, int numToFill, fl::u8 initialhue,
|
||||
fl::u8 deltahue = 5);
|
||||
|
||||
/// Fill a range of LEDs with a rainbow of colors, so that the hues
|
||||
/// are continuous between the end of the strip and the beginning.
|
||||
/// The colors making up the rainbow are at full saturation and full
|
||||
/// value (brightness).
|
||||
/// @param targetArray a pointer to the LED array to fill
|
||||
/// @param numToFill the number of LEDs to fill in the array
|
||||
/// @param initialhue the starting hue for the rainbow
|
||||
/// @param reversed whether to progress through the rainbow hues backwards
|
||||
void fill_rainbow_circular(struct CRGB *targetArray, int numToFill,
|
||||
fl::u8 initialhue, bool reversed = false);
|
||||
|
||||
/// @copydoc fill_rainbow_circular()
|
||||
void fill_rainbow_circular(struct CHSV *targetArray, int numToFill,
|
||||
fl::u8 initialhue, bool reversed = false);
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
|
||||
/// This function can write the gradient colors either:
|
||||
///
|
||||
/// 1. Into an array of CRGBs (e.g., an leds[] array, or a CRGB palette)
|
||||
/// 2. Into an array of CHSVs (e.g. a CHSV palette).
|
||||
///
|
||||
/// In the case of writing into a CRGB array, the gradient is
|
||||
/// computed in HSV space, and then HSV values are converted to RGB
|
||||
/// as they're written into the CRGB array.
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param startpos the starting position in the array
|
||||
/// @param startcolor the starting color for the gradient
|
||||
/// @param endpos the ending position in the array
|
||||
/// @param endcolor the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 startpos, CHSV startcolor,
|
||||
u16 endpos, CHSV endcolor,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
// if the points are in the wrong order, straighten them
|
||||
if (endpos < startpos) {
|
||||
u16 t = endpos;
|
||||
CHSV tc = endcolor;
|
||||
endcolor = startcolor;
|
||||
endpos = startpos;
|
||||
startpos = t;
|
||||
startcolor = tc;
|
||||
}
|
||||
|
||||
// If we're fading toward black (val=0) or white (sat=0),
|
||||
// then set the endhue to the starthue.
|
||||
// This lets us ramp smoothly to black or white, regardless
|
||||
// of what 'hue' was set in the endcolor (since it doesn't matter)
|
||||
if (endcolor.value == 0 || endcolor.saturation == 0) {
|
||||
endcolor.hue = startcolor.hue;
|
||||
}
|
||||
|
||||
// Similarly, if we're fading in from black (val=0) or white (sat=0)
|
||||
// then set the starthue to the endhue.
|
||||
// This lets us ramp smoothly up from black or white, regardless
|
||||
// of what 'hue' was set in the startcolor (since it doesn't matter)
|
||||
if (startcolor.value == 0 || startcolor.saturation == 0) {
|
||||
startcolor.hue = endcolor.hue;
|
||||
}
|
||||
|
||||
saccum87 huedistance87;
|
||||
saccum87 satdistance87;
|
||||
saccum87 valdistance87;
|
||||
|
||||
satdistance87 = (endcolor.sat - startcolor.sat) << 7;
|
||||
valdistance87 = (endcolor.val - startcolor.val) << 7;
|
||||
|
||||
fl::u8 huedelta8 = endcolor.hue - startcolor.hue;
|
||||
|
||||
if (directionCode == SHORTEST_HUES) {
|
||||
directionCode = FORWARD_HUES;
|
||||
if (huedelta8 > 127) {
|
||||
directionCode = BACKWARD_HUES;
|
||||
}
|
||||
}
|
||||
|
||||
if (directionCode == LONGEST_HUES) {
|
||||
directionCode = FORWARD_HUES;
|
||||
if (huedelta8 < 128) {
|
||||
directionCode = BACKWARD_HUES;
|
||||
}
|
||||
}
|
||||
|
||||
if (directionCode == FORWARD_HUES) {
|
||||
huedistance87 = huedelta8 << 7;
|
||||
} else /* directionCode == BACKWARD_HUES */
|
||||
{
|
||||
huedistance87 = (fl::u8)(256 - huedelta8) << 7;
|
||||
huedistance87 = -huedistance87;
|
||||
}
|
||||
|
||||
u16 pixeldistance = endpos - startpos;
|
||||
i16 divisor = pixeldistance ? pixeldistance : 1;
|
||||
|
||||
#if FASTLED_USE_32_BIT_GRADIENT_FILL
|
||||
// Use higher precision 32 bit math for new micros.
|
||||
i32 huedelta823 = (huedistance87 * 65536) / divisor;
|
||||
i32 satdelta823 = (satdistance87 * 65536) / divisor;
|
||||
i32 valdelta823 = (valdistance87 * 65536) / divisor;
|
||||
|
||||
huedelta823 *= 2;
|
||||
satdelta823 *= 2;
|
||||
valdelta823 *= 2;
|
||||
u32 hue824 = static_cast<u32>(startcolor.hue) << 24;
|
||||
u32 sat824 = static_cast<u32>(startcolor.sat) << 24;
|
||||
u32 val824 = static_cast<u32>(startcolor.val) << 24;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
targetArray[i] = CHSV(hue824 >> 24, sat824 >> 24, val824 >> 24);
|
||||
hue824 += huedelta823;
|
||||
sat824 += satdelta823;
|
||||
val824 += valdelta823;
|
||||
}
|
||||
#else
|
||||
// Use 8-bit math for older micros.
|
||||
saccum87 huedelta87 = huedistance87 / divisor;
|
||||
saccum87 satdelta87 = satdistance87 / divisor;
|
||||
saccum87 valdelta87 = valdistance87 / divisor;
|
||||
|
||||
huedelta87 *= 2;
|
||||
satdelta87 *= 2;
|
||||
valdelta87 *= 2;
|
||||
|
||||
accum88 hue88 = startcolor.hue << 8;
|
||||
accum88 sat88 = startcolor.sat << 8;
|
||||
accum88 val88 = startcolor.val << 8;
|
||||
for (u16 i = startpos; i <= endpos; ++i) {
|
||||
targetArray[i] = CHSV(hue88 >> 8, sat88 >> 8, val88 >> 8);
|
||||
hue88 += huedelta87;
|
||||
sat88 += satdelta87;
|
||||
val88 += valdelta87;
|
||||
}
|
||||
#endif // defined(__AVR__)
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, last, c2, directionCode);
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between three HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the middle color for the gradient
|
||||
/// @param c3 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2, const CHSV &c3,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 half = (numLeds / 2);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, half, c2, directionCode);
|
||||
fill_gradient(targetArray, half, c2, last, c3, directionCode);
|
||||
}
|
||||
|
||||
/// Fill a range of LEDs with a smooth HSV gradient between four HSV colors.
|
||||
/// @see fill_gradient()
|
||||
/// @param targetArray a pointer to the color array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the first middle color for the gradient
|
||||
/// @param c3 the second middle color for the gradient
|
||||
/// @param c4 the end color for the gradient
|
||||
/// @param directionCode the direction to travel around the color wheel
|
||||
template <typename T>
|
||||
void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1,
|
||||
const CHSV &c2, const CHSV &c3, const CHSV &c4,
|
||||
TGradientDirectionCode directionCode = SHORTEST_HUES) {
|
||||
u16 onethird = (numLeds / 3);
|
||||
u16 twothirds = ((numLeds * 2) / 3);
|
||||
u16 last = numLeds - 1;
|
||||
fill_gradient(targetArray, 0, c1, onethird, c2, directionCode);
|
||||
fill_gradient(targetArray, onethird, c2, twothirds, c3, directionCode);
|
||||
fill_gradient(targetArray, twothirds, c3, last, c4, directionCode);
|
||||
}
|
||||
|
||||
/// Convenience synonym
|
||||
#define fill_gradient_HSV fill_gradient
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors.
|
||||
/// Unlike HSV, there is no "color wheel" in RGB space, and therefore there's
|
||||
/// only one "direction" for the gradient to go. This means there's no
|
||||
/// TGradientDirectionCode parameter for direction.
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param startpos the starting position in the array
|
||||
/// @param startcolor the starting color for the gradient
|
||||
/// @param endpos the ending position in the array
|
||||
/// @param endcolor the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor,
|
||||
u16 endpos, CRGB endcolor);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between three RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the middle color for the gradient
|
||||
/// @param c3 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3);
|
||||
|
||||
/// Fill a range of LEDs with a smooth RGB gradient between four RGB colors.
|
||||
/// @see fill_gradient_RGB()
|
||||
/// @param leds a pointer to the LED array to fill
|
||||
/// @param numLeds the number of LEDs to fill
|
||||
/// @param c1 the starting color in the gradient
|
||||
/// @param c2 the first middle color for the gradient
|
||||
/// @param c3 the second middle color for the gradient
|
||||
/// @param c4 the end color for the gradient
|
||||
void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1,
|
||||
const CRGB &c2, const CRGB &c3, const CRGB &c4);
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
191
libraries/FastLED/src/fl/five_bit_hd_gamma.h
Normal file
191
libraries/FastLED/src/fl/five_bit_hd_gamma.h
Normal file
@@ -0,0 +1,191 @@
|
||||
/// @file five_bit_hd_gamma.h
|
||||
/// Declares functions for five-bit gamma correction
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/gamma.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/math.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "lib8tion/scale8.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
enum FiveBitGammaCorrectionMode {
|
||||
kFiveBitGammaCorrectionMode_Null = 0,
|
||||
kFiveBitGammaCorrectionMode_BitShift = 1
|
||||
};
|
||||
|
||||
// Applies gamma correction for the RGBV(8, 8, 8, 5) color space, where
|
||||
// the last byte is the brightness byte at 5 bits.
|
||||
// To override this five_bit_hd_gamma_bitshift you'll need to define
|
||||
// FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE in your build settings
|
||||
// then define the function anywhere in your project.
|
||||
// Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_bitshift(
|
||||
// fl::u8 r8, fl::u8 g8, fl::u8 b8,
|
||||
// fl::u8 r8_scale, fl::u8 g8_scale, fl::u8 b8_scale,
|
||||
// fl::u8* out_r8,
|
||||
// fl::u8* out_g8,
|
||||
// fl::u8* out_b8,
|
||||
// fl::u8* out_power_5bit) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
|
||||
// Force push
|
||||
|
||||
void internal_builtin_five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit);
|
||||
|
||||
// Exposed for testing.
|
||||
void five_bit_bitshift(u16 r16, u16 g16, u16 b16, fl::u8 brightness, CRGB *out,
|
||||
fl::u8 *out_power_5bit);
|
||||
|
||||
#ifdef FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE
|
||||
// This function is located somewhere else in your project, so it's declared
|
||||
// extern here.
|
||||
extern void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit);
|
||||
#else
|
||||
inline void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale,
|
||||
fl::u8 global_brightness,
|
||||
CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit) {
|
||||
internal_builtin_five_bit_hd_gamma_bitshift(
|
||||
colors, colors_scale, global_brightness, out_colors, out_power_5bit);
|
||||
}
|
||||
#endif // FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE
|
||||
|
||||
// Simple gamma correction function that converts from
|
||||
// 8-bit color component and converts it to gamma corrected 16-bit
|
||||
// color component. Fast and no memory overhead!
|
||||
// To override this function you'll need to define
|
||||
// FASTLED_FIVE_BIT_HD_GAMMA_BITSHIFT_FUNCTION_OVERRIDE in your build settings
|
||||
// and then define your own version anywhere in your project. Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_function(
|
||||
// fl::u8 r8, fl::u8 g8, fl::u8 b8,
|
||||
// u16* r16, u16* g16, u16* b16) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
#ifdef FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE
|
||||
// This function is located somewhere else in your project, so it's declared
|
||||
// extern here.
|
||||
extern void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16,
|
||||
u16 *b16);
|
||||
#else
|
||||
inline void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16,
|
||||
u16 *b16) {
|
||||
|
||||
gamma16(color, r16, g16, b16);
|
||||
}
|
||||
#endif // FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE
|
||||
|
||||
inline void internal_builtin_five_bit_hd_gamma_bitshift(
|
||||
CRGB colors, CRGB colors_scale, fl::u8 global_brightness, CRGB *out_colors,
|
||||
fl::u8 *out_power_5bit) {
|
||||
|
||||
if (global_brightness == 0) {
|
||||
*out_colors = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Gamma Correction
|
||||
u16 r16, g16, b16;
|
||||
five_bit_hd_gamma_function(colors, &r16, &g16, &b16);
|
||||
|
||||
// Step 2: Color correction step comes after gamma correction. These values
|
||||
// are assumed to be be relatively close to 255.
|
||||
if (colors_scale.r != 0xff) {
|
||||
r16 = scale16by8(r16, colors_scale.r);
|
||||
}
|
||||
if (colors_scale.g != 0xff) {
|
||||
g16 = scale16by8(g16, colors_scale.g);
|
||||
}
|
||||
if (colors_scale.b != 0xff) {
|
||||
b16 = scale16by8(b16, colors_scale.b);
|
||||
}
|
||||
|
||||
five_bit_bitshift(r16, g16, b16, global_brightness, out_colors,
|
||||
out_power_5bit);
|
||||
}
|
||||
|
||||
// Since the return value wasn't used, it has been omitted.
|
||||
// It's not clear what scale brightness is, or how it is to be applied,
|
||||
// so we assume 8 bits applied over the given rgb values.
|
||||
inline void five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16,
|
||||
uint8_t brightness, CRGB *out,
|
||||
uint8_t *out_power_5bit) {
|
||||
|
||||
// NEW in 3.10.2: A new closed form solution has been found!
|
||||
// Thank you https://github.com/gwgill!
|
||||
// It's okay if you don't know how this works, few do, but it tests
|
||||
// very well and is better than the old iterative approach which had
|
||||
// bad quantization issues (sudden jumps in brightness in certain intervals).
|
||||
|
||||
// ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31
|
||||
static uint32_t bright_scale[32] = {
|
||||
0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097,
|
||||
252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912,
|
||||
126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986,
|
||||
84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280};
|
||||
|
||||
auto max3 = [](u16 a, u16 b, u16 c) { return fl_max(fl_max(a, b), c); };
|
||||
|
||||
|
||||
if (brightness == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
}
|
||||
if (r16 == 0 && g16 == 0 && b16 == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = (brightness <= 31) ? brightness : 31;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t r8 = 0, g8 = 0, b8 = 0;
|
||||
|
||||
// Apply any brightness setting (we assume brightness is 0..255)
|
||||
if (brightness != 0xff) {
|
||||
r16 = scale16by8(r16, brightness);
|
||||
g16 = scale16by8(g16, brightness);
|
||||
b16 = scale16by8(b16, brightness);
|
||||
}
|
||||
|
||||
// Locate the largest value to set the brightness/scale factor
|
||||
uint16_t scale = max3(r16, g16, b16);
|
||||
|
||||
if (scale == 0) {
|
||||
*out = CRGB(0, 0, 0);
|
||||
*out_power_5bit = 0;
|
||||
return;
|
||||
} else {
|
||||
uint32_t scalef;
|
||||
|
||||
// Compute 5 bit quantized scale that is at or above the maximum value.
|
||||
scale = (scale + (2047 - (scale >> 5))) >> 11;
|
||||
|
||||
// Adjust the 16 bit values to account for the scale, then round to 8
|
||||
// bits
|
||||
scalef = bright_scale[scale];
|
||||
r8 = (r16 * scalef + 0x808000) >> 24;
|
||||
g8 = (g16 * scalef + 0x808000) >> 24;
|
||||
b8 = (b16 * scalef + 0x808000) >> 24;
|
||||
|
||||
*out = CRGB(r8, g8, b8);
|
||||
*out_power_5bit = static_cast<uint8_t>(scale);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
7
libraries/FastLED/src/fl/force_inline.h
Normal file
7
libraries/FastLED/src/fl/force_inline.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FASTLED_NO_FORCE_INLINE
|
||||
#define FASTLED_FORCE_INLINE inline
|
||||
#else
|
||||
#define FASTLED_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#endif
|
||||
351
libraries/FastLED/src/fl/function.h
Normal file
351
libraries/FastLED/src/fl/function.h
Normal file
@@ -0,0 +1,351 @@
|
||||
#pragma once
|
||||
#include "fl/memory.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/memfill.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/inplacenew.h"
|
||||
#include "fl/bit_cast.h"
|
||||
#include "fl/align.h"
|
||||
|
||||
#ifndef FASTLED_INLINE_LAMBDA_SIZE
|
||||
#define FASTLED_INLINE_LAMBDA_SIZE 64
|
||||
#endif
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(float-equal)
|
||||
|
||||
namespace fl {
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// is_function_pointer trait - detects function pointers like R(*)(Args...)
|
||||
//----------------------------------------------------------------------------
|
||||
template <typename T> struct is_function_pointer {
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct is_function_pointer<R(*)(Args...)> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// More or less a drop in replacement for std::function
|
||||
// function<R(Args...)>: type‐erasing "std::function" replacement
|
||||
// Supports free functions, lambdas/functors, member functions (const &
|
||||
// non‑const)
|
||||
//
|
||||
// NEW: Uses inline storage for member functions, free functions, and small
|
||||
// lambdas/functors. Only large lambdas/functors use heap allocation.
|
||||
//----------------------------------------------------------------------------
|
||||
template <typename> class function;
|
||||
|
||||
|
||||
|
||||
template <typename R, typename... Args>
|
||||
class FL_ALIGN function<R(Args...)> {
|
||||
private:
|
||||
struct CallableBase {
|
||||
virtual R invoke(Args... args) = 0;
|
||||
virtual ~CallableBase() = default;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct Callable : CallableBase {
|
||||
F f;
|
||||
Callable(F fn) : f(fn) {}
|
||||
R invoke(Args... args) override { return f(args...); }
|
||||
};
|
||||
|
||||
// Type-erased free function callable - stored inline!
|
||||
struct FreeFunctionCallable {
|
||||
R (*func_ptr)(Args...);
|
||||
|
||||
FreeFunctionCallable(R (*fp)(Args...)) : func_ptr(fp) {}
|
||||
|
||||
R invoke(Args... args) const {
|
||||
return func_ptr(args...);
|
||||
}
|
||||
};
|
||||
|
||||
// Type-erased small lambda/functor callable - stored inline!
|
||||
// Size limit for inline storage - configurable via preprocessor define
|
||||
|
||||
static constexpr fl::size kInlineLambdaSize = FASTLED_INLINE_LAMBDA_SIZE;
|
||||
|
||||
struct InlinedLambda {
|
||||
// Storage for the lambda/functor object
|
||||
// Use aligned storage to ensure proper alignment for any type
|
||||
alignas(max_align_t) char bytes[kInlineLambdaSize];
|
||||
|
||||
// Type-erased invoker and destructor function pointers
|
||||
R (*invoker)(const InlinedLambda& storage, Args... args);
|
||||
void (*destructor)(InlinedLambda& storage);
|
||||
|
||||
template <typename Function>
|
||||
InlinedLambda(Function f) {
|
||||
static_assert(sizeof(Function) <= kInlineLambdaSize,
|
||||
"Lambda/functor too large for inline storage");
|
||||
static_assert(alignof(Function) <= alignof(max_align_t),
|
||||
"Lambda/functor requires stricter alignment than storage provides");
|
||||
|
||||
// Initialize the entire storage to zero to avoid copying uninitialized memory
|
||||
fl::memfill(bytes, 0, kInlineLambdaSize);
|
||||
|
||||
// Construct the lambda/functor in-place
|
||||
new (bytes) Function(fl::move(f));
|
||||
|
||||
// Set up type-erased function pointers
|
||||
invoker = &invoke_lambda<Function>;
|
||||
destructor = &destroy_lambda<Function>;
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
InlinedLambda(const InlinedLambda& other)
|
||||
: invoker(other.invoker), destructor(other.destructor) {
|
||||
// This is tricky - we need to copy the stored object
|
||||
// For now, we'll use memcopy (works for trivially copyable types)
|
||||
fl::memcopy(bytes, other.bytes, kInlineLambdaSize);
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
InlinedLambda(InlinedLambda&& other)
|
||||
: invoker(other.invoker), destructor(other.destructor) {
|
||||
fl::memcopy(bytes, other.bytes, kInlineLambdaSize);
|
||||
// Reset the other object to prevent double destruction
|
||||
other.destructor = nullptr;
|
||||
}
|
||||
|
||||
~InlinedLambda() {
|
||||
if (destructor) {
|
||||
destructor(*this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FUNCTOR>
|
||||
static R invoke_lambda(const InlinedLambda& storage, Args... args) {
|
||||
// Use placement new to safely access the stored lambda
|
||||
alignas(FUNCTOR) char temp_storage[sizeof(FUNCTOR)];
|
||||
// Copy the lambda from storage
|
||||
fl::memcopy(temp_storage, storage.bytes, sizeof(FUNCTOR));
|
||||
// Get a properly typed pointer to the copied lambda (non-const for mutable lambdas)
|
||||
FUNCTOR* f = static_cast<FUNCTOR*>(static_cast<void*>(temp_storage));
|
||||
// Invoke the lambda
|
||||
return (*f)(args...);
|
||||
}
|
||||
|
||||
template <typename FUNCTOR>
|
||||
static void destroy_lambda(InlinedLambda& storage) {
|
||||
// For destruction, we need to call the destructor on the actual object
|
||||
// that was constructed with placement new in storage.bytes
|
||||
// We use the standard library approach: create a properly typed pointer
|
||||
// using placement new, then call the destructor through that pointer
|
||||
|
||||
// This is the standard-compliant way to get a properly typed pointer
|
||||
// to an object that was constructed with placement new
|
||||
FUNCTOR* obj_ptr = static_cast<FUNCTOR*>(static_cast<void*>(storage.bytes));
|
||||
obj_ptr->~FUNCTOR();
|
||||
}
|
||||
|
||||
R invoke(Args... args) const {
|
||||
return invoker(*this, args...);
|
||||
}
|
||||
};
|
||||
|
||||
// Type-erased member function callable base
|
||||
struct MemberCallableBase {
|
||||
virtual R invoke(Args... args) const = 0;
|
||||
virtual ~MemberCallableBase() = default;
|
||||
};
|
||||
|
||||
// Type-erased non-const member function callable
|
||||
struct NonConstMemberCallable : MemberCallableBase {
|
||||
void* obj;
|
||||
// Union to store member function pointer as raw bytes
|
||||
union MemberFuncStorage {
|
||||
char bytes[sizeof(R (NonConstMemberCallable::*)(Args...))];
|
||||
// Ensure proper alignment
|
||||
void* alignment_dummy;
|
||||
} member_func_storage;
|
||||
|
||||
// Type-erased invoker function - set at construction time
|
||||
R (*invoker)(void* obj, const MemberFuncStorage& mfp, Args... args);
|
||||
|
||||
template <typename C>
|
||||
NonConstMemberCallable(C* o, R (C::*mf)(Args...)) : obj(o) {
|
||||
// Store the member function pointer as raw bytes
|
||||
static_assert(sizeof(mf) <= sizeof(member_func_storage),
|
||||
"Member function pointer too large");
|
||||
fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf));
|
||||
// Set the invoker to a function that knows how to cast back and call
|
||||
invoker = &invoke_nonconst_member<C>;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
static R invoke_nonconst_member(void* obj, const MemberFuncStorage& mfp, Args... args) {
|
||||
C* typed_obj = static_cast<C*>(obj);
|
||||
R (C::*typed_mf)(Args...);
|
||||
fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf));
|
||||
return (typed_obj->*typed_mf)(args...);
|
||||
}
|
||||
|
||||
R invoke(Args... args) const override {
|
||||
return invoker(obj, member_func_storage, args...);
|
||||
}
|
||||
};
|
||||
|
||||
// Type-erased const member function callable
|
||||
struct ConstMemberCallable : MemberCallableBase {
|
||||
const void* obj;
|
||||
// Union to store member function pointer as raw bytes
|
||||
union MemberFuncStorage {
|
||||
char bytes[sizeof(R (ConstMemberCallable::*)(Args...) const)];
|
||||
// Ensure proper alignment
|
||||
void* alignment_dummy;
|
||||
} member_func_storage;
|
||||
|
||||
// Type-erased invoker function - set at construction time
|
||||
R (*invoker)(const void* obj, const MemberFuncStorage& mfp, Args... args);
|
||||
|
||||
template <typename C>
|
||||
ConstMemberCallable(const C* o, R (C::*mf)(Args...) const) : obj(o) {
|
||||
// Store the member function pointer as raw bytes
|
||||
static_assert(sizeof(mf) <= sizeof(member_func_storage),
|
||||
"Member function pointer too large");
|
||||
fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf));
|
||||
// Set the invoker to a function that knows how to cast back and call
|
||||
invoker = &invoke_const_member<C>;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
static R invoke_const_member(const void* obj, const MemberFuncStorage& mfp, Args... args) {
|
||||
const C* typed_obj = static_cast<const C*>(obj);
|
||||
R (C::*typed_mf)(Args...) const;
|
||||
fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf));
|
||||
return (typed_obj->*typed_mf)(args...);
|
||||
}
|
||||
|
||||
R invoke(Args... args) const override {
|
||||
return invoker(obj, member_func_storage, args...);
|
||||
}
|
||||
};
|
||||
|
||||
// Variant to store any of our callable types inline (with heap fallback for large lambdas)
|
||||
using Storage = Variant<fl::shared_ptr<CallableBase>, FreeFunctionCallable, InlinedLambda, NonConstMemberCallable, ConstMemberCallable>;
|
||||
Storage storage_;
|
||||
|
||||
// Helper function to handle default return value for void and non-void types
|
||||
template<typename ReturnType>
|
||||
typename enable_if<!is_void<ReturnType>::value, ReturnType>::type
|
||||
default_return_helper() const {
|
||||
return ReturnType{};
|
||||
}
|
||||
|
||||
template<typename ReturnType>
|
||||
typename enable_if<is_void<ReturnType>::value, ReturnType>::type
|
||||
default_return_helper() const {
|
||||
return;
|
||||
}
|
||||
|
||||
public:
|
||||
function() = default;
|
||||
|
||||
// Copy constructor - properly handle Variant alignment
|
||||
function(const function& other) : storage_(other.storage_) {}
|
||||
|
||||
// Move constructor - properly handle Variant alignment
|
||||
function(function&& other) noexcept : storage_(fl::move(other.storage_)) {}
|
||||
|
||||
// Copy assignment
|
||||
function& operator=(const function& other) {
|
||||
if (this != &other) {
|
||||
storage_ = other.storage_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
function& operator=(function&& other) noexcept {
|
||||
if (this != &other) {
|
||||
storage_ = fl::move(other.storage_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 1) Free function constructor - stored inline!
|
||||
function(R (*fp)(Args...)) {
|
||||
storage_ = FreeFunctionCallable(fp);
|
||||
}
|
||||
|
||||
// 2) Lambda/functor constructor - inline if small, heap if large
|
||||
template <typename F, typename = enable_if_t<!is_member_function_pointer<F>::value && !is_function_pointer<F>::value>>
|
||||
function(F f) {
|
||||
// Use template specialization instead of if constexpr for C++14 compatibility
|
||||
construct_lambda_or_functor(fl::move(f), typename conditional<sizeof(F) <= kInlineLambdaSize, true_type, false_type>::type{});
|
||||
}
|
||||
|
||||
// 3) non‑const member function - stored inline!
|
||||
template <typename C>
|
||||
function(R (C::*mf)(Args...), C* obj) {
|
||||
storage_ = NonConstMemberCallable(obj, mf);
|
||||
}
|
||||
|
||||
// 4) const member function - stored inline!
|
||||
template <typename C>
|
||||
function(R (C::*mf)(Args...) const, const C* obj) {
|
||||
storage_ = ConstMemberCallable(obj, mf);
|
||||
}
|
||||
|
||||
R operator()(Args... args) const {
|
||||
// Direct dispatch using type checking - efficient and simple
|
||||
if (auto* heap_callable = storage_.template ptr<fl::shared_ptr<CallableBase>>()) {
|
||||
return (*heap_callable)->invoke(args...);
|
||||
} else if (auto* free_func = storage_.template ptr<FreeFunctionCallable>()) {
|
||||
return free_func->invoke(args...);
|
||||
} else if (auto* inlined_lambda = storage_.template ptr<InlinedLambda>()) {
|
||||
return inlined_lambda->invoke(args...);
|
||||
} else if (auto* nonconst_member = storage_.template ptr<NonConstMemberCallable>()) {
|
||||
return nonconst_member->invoke(args...);
|
||||
} else if (auto* const_member = storage_.template ptr<ConstMemberCallable>()) {
|
||||
return const_member->invoke(args...);
|
||||
}
|
||||
// This should never happen if the function is properly constructed
|
||||
return default_return_helper<R>();
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !storage_.empty();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
storage_ = Storage{}; // Reset to empty variant
|
||||
}
|
||||
|
||||
bool operator==(const function& o) const {
|
||||
// For simplicity, just check if both are empty or both are non-empty
|
||||
// Full equality would require more complex comparison logic
|
||||
return storage_.empty() == o.storage_.empty();
|
||||
}
|
||||
|
||||
bool operator!=(const function& o) const {
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
private:
|
||||
// Helper for small lambdas/functors - inline storage
|
||||
template <typename F>
|
||||
void construct_lambda_or_functor(F f, true_type /* small */) {
|
||||
storage_ = InlinedLambda(fl::move(f));
|
||||
}
|
||||
|
||||
// Helper for large lambdas/functors - heap storage
|
||||
template <typename F>
|
||||
void construct_lambda_or_functor(F f, false_type /* large */) {
|
||||
storage_ = fl::shared_ptr<CallableBase>(fl::make_shared<Callable<F>>(fl::move(f)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
87
libraries/FastLED/src/fl/function_list.h
Normal file
87
libraries/FastLED/src/fl/function_list.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "fl/function.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/type_traits.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename FunctionType> class FunctionListBase {
|
||||
protected:
|
||||
fl::vector<pair<int, FunctionType>> mFunctions;
|
||||
int mCounter = 0;
|
||||
|
||||
public:
|
||||
// Iterator types
|
||||
using iterator = typename fl::vector<pair<int, FunctionType>>::iterator;
|
||||
using const_iterator = typename fl::vector<pair<int, FunctionType>>::const_iterator;
|
||||
|
||||
FunctionListBase() = default;
|
||||
~FunctionListBase() = default;
|
||||
|
||||
int add(FunctionType function) {
|
||||
int id = mCounter++;
|
||||
pair<int, FunctionType> entry(id, function);
|
||||
mFunctions.push_back(entry);
|
||||
return id;
|
||||
}
|
||||
|
||||
void remove(int id) {
|
||||
for (int i = mFunctions.size() - 1; i >= 0; --i) {
|
||||
if (mFunctions[i].first == id) {
|
||||
mFunctions.erase(mFunctions.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void clear() { mFunctions.clear(); }
|
||||
|
||||
// Iterator methods
|
||||
iterator begin() { return mFunctions.begin(); }
|
||||
iterator end() { return mFunctions.end(); }
|
||||
const_iterator begin() const { return mFunctions.begin(); }
|
||||
const_iterator end() const { return mFunctions.end(); }
|
||||
const_iterator cbegin() const { return mFunctions.cbegin(); }
|
||||
const_iterator cend() const { return mFunctions.cend(); }
|
||||
|
||||
// Size information
|
||||
fl::size size() const { return mFunctions.size(); }
|
||||
bool empty() const { return mFunctions.empty(); }
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
class FunctionList : public FunctionListBase<function<void(Args...)>> {
|
||||
public:
|
||||
void invoke(Args... args) {
|
||||
for (const auto &pair : this->mFunctions) {
|
||||
auto &function = pair.second;
|
||||
function(args...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class FunctionList<void> : public FunctionListBase<function<void()>> {
|
||||
public:
|
||||
void invoke() {
|
||||
for (const auto &pair : this->mFunctions) {
|
||||
auto &function = pair.second;
|
||||
function();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class FunctionList<void()> : public FunctionListBase<function<void()>> {
|
||||
public:
|
||||
void invoke() {
|
||||
for (const auto &pair : this->mFunctions) {
|
||||
auto &function = pair.second;
|
||||
function();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
97
libraries/FastLED/src/fl/functional.h
Normal file
97
libraries/FastLED/src/fl/functional.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/utility.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T>
|
||||
class Ptr; // Forward declare Ptr to avoid header inclusion
|
||||
|
||||
template <typename T, typename Deleter>
|
||||
class unique_ptr; // Forward declare unique_ptr to avoid header inclusion
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// invoke implementation - equivalent to std::invoke from C++17
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper to detect member data pointers
|
||||
template <typename T>
|
||||
struct is_member_data_pointer : false_type {};
|
||||
|
||||
template <typename T, typename C>
|
||||
struct is_member_data_pointer<T C::*> : integral_constant<bool, !is_function<T>::value> {};
|
||||
|
||||
// Helper to detect if T is a pointer type
|
||||
template <typename T>
|
||||
struct is_pointer_like : false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_pointer_like<T*> : true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_pointer_like<fl::Ptr<T>> : true_type {};
|
||||
|
||||
template <typename T, typename Deleter>
|
||||
struct is_pointer_like<fl::unique_ptr<T, Deleter>> : true_type {};
|
||||
|
||||
// Helper to detect if we should use pointer-to-member syntax
|
||||
template <typename T>
|
||||
struct use_pointer_syntax : is_pointer_like<typename remove_reference<T>::type> {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Main invoke function overloads
|
||||
|
||||
// 1a. Member function pointer with object reference
|
||||
template <typename F, typename T1, typename... Args>
|
||||
auto invoke(F&& f, T1&& t1, Args&&... args)
|
||||
-> enable_if_t<is_member_function_pointer<typename remove_reference<F>::type>::value &&
|
||||
!detail::use_pointer_syntax<T1>::value,
|
||||
decltype((fl::forward<T1>(t1).*f)(fl::forward<Args>(args)...))>
|
||||
{
|
||||
return (fl::forward<T1>(t1).*f)(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// 1b. Member function pointer with pointer-like object
|
||||
template <typename F, typename T1, typename... Args>
|
||||
auto invoke(F&& f, T1&& t1, Args&&... args)
|
||||
-> enable_if_t<is_member_function_pointer<typename remove_reference<F>::type>::value &&
|
||||
detail::use_pointer_syntax<T1>::value,
|
||||
decltype(((*fl::forward<T1>(t1)).*f)(fl::forward<Args>(args)...))>
|
||||
{
|
||||
return ((*fl::forward<T1>(t1)).*f)(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// 2a. Member data pointer with object reference
|
||||
template <typename F, typename T1>
|
||||
auto invoke(F&& f, T1&& t1)
|
||||
-> enable_if_t<detail::is_member_data_pointer<typename remove_reference<F>::type>::value &&
|
||||
!detail::use_pointer_syntax<T1>::value,
|
||||
decltype(fl::forward<T1>(t1).*f)>
|
||||
{
|
||||
return fl::forward<T1>(t1).*f;
|
||||
}
|
||||
|
||||
// 2b. Member data pointer with pointer-like object
|
||||
template <typename F, typename T1>
|
||||
auto invoke(F&& f, T1&& t1)
|
||||
-> enable_if_t<detail::is_member_data_pointer<typename remove_reference<F>::type>::value &&
|
||||
detail::use_pointer_syntax<T1>::value,
|
||||
decltype((*fl::forward<T1>(t1)).*f)>
|
||||
{
|
||||
return (*fl::forward<T1>(t1)).*f;
|
||||
}
|
||||
|
||||
// 3. Regular callable (function pointer, lambda, functor)
|
||||
template <typename F, typename... Args>
|
||||
auto invoke(F&& f, Args&&... args)
|
||||
-> enable_if_t<!is_member_function_pointer<typename remove_reference<F>::type>::value &&
|
||||
!detail::is_member_data_pointer<typename remove_reference<F>::type>::value,
|
||||
decltype(fl::forward<F>(f)(fl::forward<Args>(args)...))>
|
||||
{
|
||||
return fl::forward<F>(f)(fl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
8
libraries/FastLED/src/fl/gamma.cpp
Normal file
8
libraries/FastLED/src/fl/gamma.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "fl/gamma.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// gamma_2_8 lookup table has been moved to fl/ease.cpp.hpp
|
||||
// This file is kept for compatibility with the build system
|
||||
|
||||
} // namespace fl
|
||||
19
libraries/FastLED/src/fl/gamma.h
Normal file
19
libraries/FastLED/src/fl/gamma.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/ease.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Forward declaration - gamma_2_8 is now defined in fl/ease.h
|
||||
extern const u16 gamma_2_8[256];
|
||||
|
||||
inline void gamma16(const CRGB &rgb, u16* r16, u16* g16, u16* b16) {
|
||||
*r16 = gamma_2_8[rgb.r];
|
||||
*g16 = gamma_2_8[rgb.g];
|
||||
*b16 = gamma_2_8[rgb.b];
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
478
libraries/FastLED/src/fl/geometry.h
Normal file
478
libraries/FastLED/src/fl/geometry.h
Normal file
@@ -0,0 +1,478 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/int.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/move.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_FLOAT_CONVERSION
|
||||
FL_DISABLE_WARNING_SIGN_CONVERSION
|
||||
FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T> struct vec3 {
|
||||
// value_type
|
||||
using value_type = T;
|
||||
T x = 0;
|
||||
T y = 0;
|
||||
T z = 0;
|
||||
constexpr vec3() = default;
|
||||
constexpr vec3(T x, T y, T z) : x(x), y(y), z(z) {}
|
||||
|
||||
template <typename U>
|
||||
explicit constexpr vec3(U xyz) : x(xyz), y(xyz), z(xyz) {}
|
||||
|
||||
constexpr vec3(const vec3 &p) = default;
|
||||
constexpr vec3(vec3 &&p) noexcept = default;
|
||||
vec3 &operator=(vec3 &&p) noexcept = default;
|
||||
|
||||
vec3 &operator*=(const float &f) {
|
||||
x *= f;
|
||||
y *= f;
|
||||
z *= f;
|
||||
return *this;
|
||||
}
|
||||
vec3 &operator/=(const float &f) {
|
||||
x /= f;
|
||||
y /= f;
|
||||
z /= f;
|
||||
return *this;
|
||||
}
|
||||
vec3 &operator*=(const double &f) {
|
||||
x *= f;
|
||||
y *= f;
|
||||
z *= f;
|
||||
return *this;
|
||||
}
|
||||
vec3 &operator/=(const double &f) {
|
||||
x /= f;
|
||||
y /= f;
|
||||
z /= f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator/=(const u16 &d) {
|
||||
x /= d;
|
||||
y /= d;
|
||||
z /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator/=(const int &d) {
|
||||
x /= d;
|
||||
y /= d;
|
||||
z /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator/=(const vec3 &p) {
|
||||
x /= p.x;
|
||||
y /= p.y;
|
||||
z /= p.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator+=(const vec3 &p) {
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
z += p.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator-=(const vec3 &p) {
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
z -= p.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 &operator=(const vec3 &p) {
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
z = p.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 operator-(const vec3 &p) const {
|
||||
return vec3(x - p.x, y - p.y, z - p.z);
|
||||
}
|
||||
|
||||
vec3 operator+(const vec3 &p) const {
|
||||
return vec3(x + p.x, y + p.y, z + p.z);
|
||||
}
|
||||
|
||||
vec3 operator*(const vec3 &p) const {
|
||||
return vec3(x * p.x, y * p.y, z * p.z);
|
||||
}
|
||||
|
||||
vec3 operator/(const vec3 &p) const {
|
||||
return vec3(x / p.x, y / p.y, z / p.z);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec3 operator+(const NumberT &p) const {
|
||||
return vec3(x + p, y + p, z + p);
|
||||
}
|
||||
|
||||
template <typename U> vec3 operator+(const vec3<U> &p) const {
|
||||
return vec3(x + p.x, y + p.y, z + p.z);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec3 operator-(const NumberT &p) const {
|
||||
return vec3(x - p, y - p, z - p);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec3 operator*(const NumberT &p) const {
|
||||
return vec3(x * p, y * p, z * p);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec3 operator/(const NumberT &p) const {
|
||||
T a = x / p;
|
||||
T b = y / p;
|
||||
T c = z / p;
|
||||
return vec3<T>(a, b, c);
|
||||
}
|
||||
|
||||
bool operator==(const vec3 &p) const {
|
||||
return (x == p.x && y == p.y && z == p.z);
|
||||
}
|
||||
|
||||
bool operator!=(const vec3 &p) const {
|
||||
return (x != p.x || y != p.y || z != p.z);
|
||||
}
|
||||
|
||||
template <typename U> bool operator==(const vec3<U> &p) const {
|
||||
return (x == p.x && y == p.y && z == p.z);
|
||||
}
|
||||
|
||||
template <typename U> bool operator!=(const vec3<U> &p) const {
|
||||
return (x != p.x || y != p.y || z != p.z);
|
||||
}
|
||||
|
||||
vec3 getMax(const vec3 &p) const {
|
||||
return vec3(MAX(x, p.x), MAX(y, p.y), MAX(z, p.z));
|
||||
}
|
||||
|
||||
vec3 getMin(const vec3 &p) const {
|
||||
return vec3(MIN(x, p.x), MIN(y, p.y), MIN(z, p.z));
|
||||
}
|
||||
|
||||
template <typename U> vec3<U> cast() const {
|
||||
return vec3<U>(static_cast<U>(x), static_cast<U>(y), static_cast<U>(z));
|
||||
}
|
||||
|
||||
template <typename U> vec3 getMax(const vec3<U> &p) const {
|
||||
return vec3<U>(MAX(x, p.x), MAX(y, p.y), MAX(z, p.z));
|
||||
}
|
||||
|
||||
template <typename U> vec3 getMin(const vec3<U> &p) const {
|
||||
return vec3<U>(MIN(x, p.x), MIN(y, p.y), MIN(z, p.z));
|
||||
}
|
||||
|
||||
T distance(const vec3 &p) const {
|
||||
T dx = x - p.x;
|
||||
T dy = y - p.y;
|
||||
T dz = z - p.z;
|
||||
return sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
bool is_zero() const { return (x == 0 && y == 0 && z == 0); }
|
||||
};
|
||||
|
||||
using vec3f = vec3<float>; // Full precision but slow.
|
||||
|
||||
template <typename T> struct vec2 {
|
||||
// value_type
|
||||
using value_type = T;
|
||||
value_type x = 0;
|
||||
value_type y = 0;
|
||||
constexpr vec2() = default;
|
||||
constexpr vec2(T x, T y) : x(x), y(y) {}
|
||||
|
||||
template <typename U> explicit constexpr vec2(U xy) : x(xy), y(xy) {}
|
||||
|
||||
constexpr vec2(const vec2 &p) = default;
|
||||
constexpr vec2(vec2 &&p) noexcept = default;
|
||||
vec2 &operator=(vec2 &&p) noexcept = default;
|
||||
|
||||
vec2 &operator*=(const float &f) {
|
||||
x *= f;
|
||||
y *= f;
|
||||
return *this;
|
||||
}
|
||||
vec2 &operator/=(const float &f) {
|
||||
// *this = point_xy_math::div(*this, f);
|
||||
x /= f;
|
||||
y /= f;
|
||||
return *this;
|
||||
}
|
||||
vec2 &operator*=(const double &f) {
|
||||
// *this = point_xy_math::mul(*this, f);
|
||||
x *= f;
|
||||
y *= f;
|
||||
return *this;
|
||||
}
|
||||
vec2 &operator/=(const double &f) {
|
||||
// *this = point_xy_math::div(*this, f);
|
||||
x /= f;
|
||||
y /= f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator/=(const u16 &d) {
|
||||
// *this = point_xy_math::div(*this, d);
|
||||
x /= d;
|
||||
y /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator/=(const int &d) {
|
||||
// *this = point_xy_math::div(*this, d);
|
||||
x /= d;
|
||||
y /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator/=(const vec2 &p) {
|
||||
// *this = point_xy_math::div(*this, p);
|
||||
x /= p.x;
|
||||
y /= p.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator+=(const vec2 &p) {
|
||||
//*this = point_xy_math::add(*this, p);
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator-=(const vec2 &p) {
|
||||
// *this = point_xy_math::sub(*this, p);
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 &operator=(const vec2 &p) {
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec2 operator-(const vec2 &p) const { return vec2(x - p.x, y - p.y); }
|
||||
|
||||
vec2 operator+(const vec2 &p) const { return vec2(x + p.x, y + p.y); }
|
||||
|
||||
vec2 operator*(const vec2 &p) const { return vec2(x * p.x, y * p.y); }
|
||||
|
||||
vec2 operator/(const vec2 &p) const { return vec2(x / p.x, y / p.y); }
|
||||
|
||||
template <typename NumberT> vec2 operator+(const NumberT &p) const {
|
||||
return vec2(x + p, y + p);
|
||||
}
|
||||
|
||||
template <typename U> vec2 operator+(const vec2<U> &p) const {
|
||||
return vec2(x + p.x, y + p.x);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec2 operator-(const NumberT &p) const {
|
||||
return vec2(x - p, y - p);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec2 operator*(const NumberT &p) const {
|
||||
return vec2(x * p, y * p);
|
||||
}
|
||||
|
||||
template <typename NumberT> vec2 operator/(const NumberT &p) const {
|
||||
T a = x / p;
|
||||
T b = y / p;
|
||||
return vec2<T>(a, b);
|
||||
}
|
||||
|
||||
bool operator==(const vec2 &p) const { return (x == p.x && y == p.y); }
|
||||
|
||||
bool operator!=(const vec2 &p) const { return (x != p.x || y != p.y); }
|
||||
|
||||
template <typename U> bool operator==(const vec2<U> &p) const {
|
||||
return (x == p.x && y == p.y);
|
||||
}
|
||||
|
||||
template <typename U> bool operator!=(const vec2<U> &p) const {
|
||||
return (x != p.x || y != p.y);
|
||||
}
|
||||
|
||||
vec2 getMax(const vec2 &p) const { return vec2(MAX(x, p.x), MAX(y, p.y)); }
|
||||
|
||||
vec2 getMin(const vec2 &p) const { return vec2(MIN(x, p.x), MIN(y, p.y)); }
|
||||
|
||||
template <typename U> vec2<U> cast() const {
|
||||
return vec2<U>(static_cast<U>(x), static_cast<U>(y));
|
||||
}
|
||||
|
||||
template <typename U> vec2 getMax(const vec2<U> &p) const {
|
||||
return vec2<U>(MAX(x, p.x), MAX(y, p.y));
|
||||
}
|
||||
|
||||
template <typename U> vec2 getMin(const vec2<U> &p) const {
|
||||
return vec2<U>(MIN(x, p.x), MIN(y, p.y));
|
||||
}
|
||||
|
||||
T distance(const vec2 &p) const {
|
||||
T dx = x - p.x;
|
||||
T dy = y - p.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
bool is_zero() const { return (x == 0 && y == 0); }
|
||||
};
|
||||
|
||||
using vec2f = vec2<float>; // Full precision but slow.
|
||||
using vec2u8 = vec2<fl::u8>; // 8-bit unsigned integer vector.
|
||||
using vec2i16 = vec2<i16>; // 16-bit signed integer vector.
|
||||
|
||||
// Legacy support for vec3
|
||||
using pair_xyz_float = vec3<float>; // Legacy name for vec3f
|
||||
|
||||
// Legacy support for vec2
|
||||
|
||||
using pair_xy_float = vec2<float>; // Legacy name for vec2f
|
||||
|
||||
// pair_xy<T> is the legacy name for vec2<T>
|
||||
template <typename T> struct pair_xy : public vec2<T> {
|
||||
using value_type = T;
|
||||
using vec2<T>::vec2;
|
||||
pair_xy() = default;
|
||||
pair_xy(const vec2<T> &p) : vec2<T>(p) {}
|
||||
};
|
||||
|
||||
template <typename T> struct line_xy {
|
||||
vec2<T> start;
|
||||
vec2<T> end;
|
||||
|
||||
line_xy() = default;
|
||||
line_xy(const vec2<T> &start, const vec2<T> &end)
|
||||
: start(start), end(end) {}
|
||||
|
||||
line_xy(T start_x, T start_y, T end_x, T end_y)
|
||||
: start(start_x, start_y), end(end_x, end_y) {}
|
||||
|
||||
line_xy(const line_xy &other) = default;
|
||||
line_xy &operator=(const line_xy &other) = default;
|
||||
line_xy(line_xy &&other) noexcept = default;
|
||||
line_xy &operator=(line_xy &&other) noexcept = default;
|
||||
|
||||
bool empty() const { return (start == end); }
|
||||
|
||||
float distance_to(const vec2<T> &p,
|
||||
vec2<T> *out_projected = nullptr) const {
|
||||
return distance_to_line_with_point(p, start, end, out_projected);
|
||||
}
|
||||
|
||||
private:
|
||||
// Computes the closest distance from `p` to the line through `a` and `b`,
|
||||
// and writes the projected point.
|
||||
static float distance_to_line_with_point(vec2<T> p, vec2<T> a, vec2<T> b,
|
||||
vec2<T> *out_projected) {
|
||||
vec2<T> maybe;
|
||||
vec2<T> &out_proj = out_projected ? *out_projected : maybe;
|
||||
float dx = b.x - a.x;
|
||||
float dy = b.y - a.y;
|
||||
float len_sq = dx * dx + dy * dy;
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(float-equal)
|
||||
const bool is_zero = (len_sq == 0.0f);
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
if (is_zero) {
|
||||
// a == b, the segment is a point
|
||||
out_proj = a;
|
||||
dx = p.x - a.x;
|
||||
dy = p.y - a.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
// Project point p onto the line segment, computing parameter t
|
||||
float t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len_sq;
|
||||
|
||||
// Clamp t to [0,1] to stay within the segment
|
||||
if (t < 0.0f)
|
||||
t = 0.0f;
|
||||
else if (t > 1.0f)
|
||||
t = 1.0f;
|
||||
|
||||
// Find the closest point
|
||||
out_proj.x = a.x + t * dx;
|
||||
out_proj.y = a.y + t * dy;
|
||||
|
||||
dx = p.x - out_proj.x;
|
||||
dy = p.y - out_proj.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct rect {
|
||||
vec2<T> mMin;
|
||||
vec2<T> mMax;
|
||||
|
||||
rect() = default;
|
||||
rect(const vec2<T> &min, const vec2<T> &max) : mMin(min), mMax(max) {}
|
||||
|
||||
rect(T min_x, T min_y, T max_x, T max_y)
|
||||
: mMin(min_x, min_y), mMax(max_x, max_y) {}
|
||||
|
||||
rect(const rect &other) = default;
|
||||
rect &operator=(const rect &other) = default;
|
||||
rect(rect &&other) noexcept = default;
|
||||
rect &operator=(rect &&other) noexcept = default;
|
||||
|
||||
u16 width() const { return mMax.x - mMin.x; }
|
||||
|
||||
u16 height() const { return mMax.y - mMin.y; }
|
||||
|
||||
bool empty() const { return (mMin.x == mMax.x && mMin.y == mMax.y); }
|
||||
|
||||
void expand(const vec2<T> &p) { expand(p.x, p.y); }
|
||||
|
||||
void expand(const rect &r) {
|
||||
expand(r.mMin);
|
||||
expand(r.mMax);
|
||||
}
|
||||
|
||||
void expand(T x, T y) {
|
||||
mMin.x = MIN(mMin.x, x);
|
||||
mMin.y = MIN(mMin.y, y);
|
||||
mMax.x = MAX(mMax.x, x);
|
||||
mMax.y = MAX(mMax.y, y);
|
||||
}
|
||||
|
||||
bool contains(const vec2<T> &p) const {
|
||||
return (p.x >= mMin.x && p.x < mMax.x && p.y >= mMin.y && p.y < mMax.y);
|
||||
}
|
||||
|
||||
bool contains(const T &x, const T &y) const {
|
||||
return contains(vec2<T>(x, y));
|
||||
}
|
||||
|
||||
bool operator==(const rect &r) const {
|
||||
return (mMin == r.mMin && mMax == r.mMax);
|
||||
}
|
||||
|
||||
bool operator!=(const rect &r) const { return !(*this == r); }
|
||||
|
||||
template <typename U> bool operator==(const rect<U> &r) const {
|
||||
return (mMin == r.mMin && mMax == r.mMax);
|
||||
}
|
||||
|
||||
template <typename U> bool operator!=(const rect<U> &r) const {
|
||||
return !(*this == r);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
147
libraries/FastLED/src/fl/gradient.cpp
Normal file
147
libraries/FastLED/src/fl/gradient.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
#include "fl/gradient.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/colorutils.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
struct Visitor {
|
||||
Visitor(u8 index) : index(index) {}
|
||||
void accept(const CRGBPalette16 *palette) {
|
||||
CRGB c = ColorFromPalette(*palette, index);
|
||||
return_val = c;
|
||||
}
|
||||
|
||||
void accept(const CRGBPalette32 *palette) {
|
||||
CRGB c = ColorFromPalette(*palette, index);
|
||||
return_val = c;
|
||||
}
|
||||
|
||||
void accept(const CRGBPalette256 *palette) {
|
||||
CRGB c = ColorFromPaletteExtended(*palette, index);
|
||||
return_val = c;
|
||||
}
|
||||
|
||||
void accept(const Gradient::GradientFunction &func) {
|
||||
CRGB c = func(index);
|
||||
return_val = c;
|
||||
}
|
||||
|
||||
template <typename T> void accept(const T &obj) {
|
||||
// This should never be called, but we need to provide a default
|
||||
// implementation to avoid compilation errors.
|
||||
accept(&obj);
|
||||
}
|
||||
|
||||
CRGB return_val;
|
||||
u8 index;
|
||||
};
|
||||
|
||||
struct VisitorFill {
|
||||
VisitorFill(span<const u8> indices, span<CRGB> output)
|
||||
: output(output), indices(indices) {
|
||||
// This assert was triggering on the corkscrew example. Not sure why
|
||||
// but the corrective action of taking the min was corrective action.
|
||||
// FASTLED_ASSERT(
|
||||
// indices.size() == output.size(),
|
||||
// "Gradient::fill: indices and output must be the same size"
|
||||
// "\nSize was" << indices.size() << " and " << output.size());
|
||||
n = MIN(indices.size(), output.size());
|
||||
}
|
||||
void accept(const CRGBPalette16 *palette) {
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
output[i] = ColorFromPalette(*palette, indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void accept(const CRGBPalette32 *palette) {
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
output[i] = ColorFromPalette(*palette, indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void accept(const CRGBPalette256 *palette) {
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
output[i] = ColorFromPaletteExtended(*palette, indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void accept(const Gradient::GradientFunction &func) {
|
||||
for (fl::size i = 0; i < n; ++i) {
|
||||
output[i] = func(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void accept(const T &obj) {
|
||||
// This should never be called, but we need to provide a default
|
||||
// implementation to avoid compilation errors.
|
||||
accept(&obj);
|
||||
}
|
||||
|
||||
span<CRGB> output;
|
||||
span<const u8> indices;
|
||||
u8 n = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CRGB Gradient::colorAt(u8 index) const {
|
||||
Visitor visitor(index);
|
||||
mVariant.visit(visitor);
|
||||
return visitor.return_val;
|
||||
}
|
||||
|
||||
template <typename T> Gradient::Gradient(T *palette) { set(palette); }
|
||||
|
||||
Gradient::Gradient(const Gradient &other) : mVariant(other.mVariant) {}
|
||||
|
||||
Gradient::Gradient(Gradient &&other) noexcept
|
||||
: mVariant(move(other.mVariant)) {}
|
||||
|
||||
void Gradient::set(const CRGBPalette32 *palette) { mVariant = palette; }
|
||||
|
||||
void Gradient::set(const CRGBPalette256 *palette) { mVariant = palette; }
|
||||
|
||||
void Gradient::set(const CRGBPalette16 *palette) { mVariant = palette; }
|
||||
|
||||
void Gradient::set(const GradientFunction &func) { mVariant = func; }
|
||||
|
||||
Gradient &Gradient::operator=(const Gradient &other) {
|
||||
if (this != &other) {
|
||||
mVariant = other.mVariant;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Gradient::fill(span<const u8> input, span<CRGB> output) const {
|
||||
VisitorFill visitor(input, output);
|
||||
mVariant.visit(visitor);
|
||||
}
|
||||
|
||||
CRGB GradientInlined::colorAt(u8 index) const {
|
||||
Visitor visitor(index);
|
||||
mVariant.visit(visitor);
|
||||
return visitor.return_val;
|
||||
}
|
||||
void GradientInlined::fill(span<const u8> input,
|
||||
span<CRGB> output) const {
|
||||
VisitorFill visitor(input, output);
|
||||
mVariant.visit(visitor);
|
||||
}
|
||||
|
||||
Gradient::Gradient(const GradientInlined &other) {
|
||||
// Visitor is cumbersome but guarantees all paths are handled.
|
||||
struct Copy {
|
||||
Copy(Gradient &owner) : mOwner(owner) {}
|
||||
void accept(const CRGBPalette16 &palette) { mOwner.set(&palette); }
|
||||
void accept(const CRGBPalette32 &palette) { mOwner.set(&palette); }
|
||||
void accept(const CRGBPalette256 &palette) { mOwner.set(&palette); }
|
||||
void accept(const GradientFunction &func) { mOwner.set(func); }
|
||||
Gradient &mOwner;
|
||||
};
|
||||
Copy copy_to_self(*this);
|
||||
other.variant().visit(copy_to_self);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
71
libraries/FastLED/src/fl/gradient.h
Normal file
71
libraries/FastLED/src/fl/gradient.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/colorutils.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/variant.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class CRGBPalette16;
|
||||
class CRGBPalette32;
|
||||
class CRGBPalette256;
|
||||
class GradientInlined;
|
||||
|
||||
class Gradient {
|
||||
public:
|
||||
using GradientFunction = fl::function<CRGB(u8 index)>;
|
||||
Gradient() = default;
|
||||
Gradient(const GradientInlined &other);
|
||||
|
||||
template <typename T> Gradient(T *palette);
|
||||
Gradient(const Gradient &other);
|
||||
Gradient &operator=(const Gradient &other);
|
||||
|
||||
Gradient(Gradient &&other) noexcept;
|
||||
|
||||
// non template allows carefull control of what can be set.
|
||||
void set(const CRGBPalette16 *palette);
|
||||
void set(const CRGBPalette32 *palette);
|
||||
void set(const CRGBPalette256 *palette);
|
||||
void set(const GradientFunction &func);
|
||||
|
||||
CRGB colorAt(u8 index) const;
|
||||
void fill(span<const u8> input, span<CRGB> output) const;
|
||||
|
||||
private:
|
||||
using GradientVariant =
|
||||
Variant<const CRGBPalette16 *, const CRGBPalette32 *,
|
||||
const CRGBPalette256 *, GradientFunction>;
|
||||
GradientVariant mVariant;
|
||||
};
|
||||
|
||||
class GradientInlined {
|
||||
public:
|
||||
using GradientFunction = fl::function<CRGB(u8 index)>;
|
||||
using GradientVariant =
|
||||
Variant<CRGBPalette16, CRGBPalette32, CRGBPalette256, GradientFunction>;
|
||||
GradientInlined() = default;
|
||||
|
||||
template <typename T> GradientInlined(const T &palette) { set(palette); }
|
||||
|
||||
GradientInlined(const GradientInlined &other) = default;
|
||||
GradientInlined &operator=(const GradientInlined &other) = default;
|
||||
|
||||
void set(const CRGBPalette16 &palette) { mVariant = palette; }
|
||||
void set(const CRGBPalette32 &palette) { mVariant = palette; }
|
||||
void set(const CRGBPalette256 &palette) { mVariant = palette; }
|
||||
void set(const GradientFunction &func) { mVariant = func; }
|
||||
|
||||
CRGB colorAt(u8 index) const;
|
||||
void fill(span<const u8> input, span<CRGB> output) const;
|
||||
|
||||
GradientVariant &variant() { return mVariant; }
|
||||
const GradientVariant &variant() const { return mVariant; }
|
||||
|
||||
private:
|
||||
GradientVariant mVariant;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
92
libraries/FastLED/src/fl/grid.h
Normal file
92
libraries/FastLED/src/fl/grid.h
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/allocator.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
template <typename T> class Grid {
|
||||
public:
|
||||
Grid() = default;
|
||||
|
||||
Grid(u32 width, u32 height) { reset(width, height); }
|
||||
|
||||
void reset(u32 width, u32 height) {
|
||||
clear();
|
||||
if (width != mWidth || height != mHeight) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mData.resize(width * height);
|
||||
|
||||
}
|
||||
mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
|
||||
width, height);
|
||||
}
|
||||
|
||||
|
||||
void clear() {
|
||||
for (u32 i = 0; i < mWidth * mHeight; ++i) {
|
||||
mData[i] = T();
|
||||
}
|
||||
}
|
||||
|
||||
vec2<T> minMax() const {
|
||||
T minValue = mData[0];
|
||||
T maxValue = mData[0];
|
||||
for (u32 i = 1; i < mWidth * mHeight; ++i) {
|
||||
if (mData[i] < minValue) {
|
||||
minValue = mData[i];
|
||||
}
|
||||
if (mData[i] > maxValue) {
|
||||
maxValue = mData[i];
|
||||
}
|
||||
}
|
||||
// *min = minValue;
|
||||
// *max = maxValue;
|
||||
vec2<T> out(minValue, maxValue);
|
||||
return out;
|
||||
}
|
||||
|
||||
T &at(u32 x, u32 y) { return access(x, y); }
|
||||
const T &at(u32 x, u32 y) const { return access(x, y); }
|
||||
|
||||
T &operator()(u32 x, u32 y) { return at(x, y); }
|
||||
const T &operator()(u32 x, u32 y) const { return at(x, y); }
|
||||
|
||||
u32 width() const { return mWidth; }
|
||||
u32 height() const { return mHeight; }
|
||||
|
||||
T* data() { return mData.data(); }
|
||||
const T* data() const { return mData.data(); }
|
||||
|
||||
fl::size size() const { return mData.size(); }
|
||||
|
||||
private:
|
||||
static T &NullValue() {
|
||||
static T gNull;
|
||||
return gNull;
|
||||
}
|
||||
T &access(u32 x, u32 y) {
|
||||
if (x < mWidth && y < mHeight) {
|
||||
return mSlice.at(x, y);
|
||||
} else {
|
||||
return NullValue(); // safe.
|
||||
}
|
||||
}
|
||||
const T &access(u32 x, u32 y) const {
|
||||
if (x < mWidth && y < mHeight) {
|
||||
return mSlice.at(x, y);
|
||||
} else {
|
||||
return NullValue(); // safe.
|
||||
}
|
||||
}
|
||||
fl::vector<T, fl::allocator_psram<T>> mData;
|
||||
u32 mWidth = 0;
|
||||
u32 mHeight = 0;
|
||||
fl::MatrixSlice<T> mSlice;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
7
libraries/FastLED/src/fl/has_include.h
Normal file
7
libraries/FastLED/src/fl/has_include.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __has_include
|
||||
#define FL_HAS_INCLUDE(x) 0
|
||||
#else
|
||||
#define FL_HAS_INCLUDE(x) __has_include(x)
|
||||
#endif
|
||||
264
libraries/FastLED/src/fl/hash.h
Normal file
264
libraries/FastLED/src/fl/hash.h
Normal file
@@ -0,0 +1,264 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/str.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/int.h"
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/force_inline.h"
|
||||
#include "fl/memfill.h"
|
||||
#include <string.h>
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename T> struct vec2;
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// MurmurHash3 x86 32-bit
|
||||
//-----------------------------------------------------------------------------
|
||||
// Based on the public‐domain implementation by Austin Appleby:
|
||||
// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
|
||||
static inline u32 MurmurHash3_x86_32(const void *key, fl::size len,
|
||||
u32 seed = 0) {
|
||||
|
||||
FL_DISABLE_WARNING_PUSH;
|
||||
FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH;
|
||||
|
||||
const fl::u8 *data = static_cast<const fl::u8 *>(key);
|
||||
const int nblocks = int(len / 4);
|
||||
|
||||
u32 h1 = seed;
|
||||
const u32 c1 = 0xcc9e2d51;
|
||||
const u32 c2 = 0x1b873593;
|
||||
|
||||
// body
|
||||
const u32 *blocks = reinterpret_cast<const u32 *>(data);
|
||||
for (int i = 0; i < nblocks; ++i) {
|
||||
u32 k1 = blocks[i];
|
||||
k1 *= c1;
|
||||
k1 = (k1 << 15) | (k1 >> 17);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = (h1 << 13) | (h1 >> 19);
|
||||
h1 = h1 * 5 + 0xe6546b64;
|
||||
}
|
||||
|
||||
// tail
|
||||
const fl::u8 *tail = data + (nblocks * 4);
|
||||
u32 k1 = 0;
|
||||
switch (len & 3) {
|
||||
case 3:
|
||||
k1 ^= u32(tail[2]) << 16;
|
||||
case 2:
|
||||
k1 ^= u32(tail[1]) << 8;
|
||||
case 1:
|
||||
k1 ^= u32(tail[0]);
|
||||
k1 *= c1;
|
||||
k1 = (k1 << 15) | (k1 >> 17);
|
||||
k1 *= c2;
|
||||
h1 ^= k1;
|
||||
}
|
||||
|
||||
// finalization
|
||||
h1 ^= u32(len);
|
||||
// fmix32
|
||||
h1 ^= h1 >> 16;
|
||||
h1 *= 0x85ebca6b;
|
||||
h1 ^= h1 >> 13;
|
||||
h1 *= 0xc2b2ae35;
|
||||
h1 ^= h1 >> 16;
|
||||
|
||||
return h1;
|
||||
|
||||
FL_DISABLE_WARNING_POP;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Fast, cheap 32-bit integer hash (Thomas Wang)
|
||||
//-----------------------------------------------------------------------------
|
||||
static inline u32 fast_hash32(u32 x) noexcept {
|
||||
x = (x ^ 61u) ^ (x >> 16);
|
||||
x = x + (x << 3);
|
||||
x = x ^ (x >> 4);
|
||||
x = x * 0x27d4eb2dU;
|
||||
x = x ^ (x >> 15);
|
||||
return x;
|
||||
}
|
||||
|
||||
// 3) Handy two-word hasher
|
||||
static inline u32 hash_pair(u32 a, u32 b,
|
||||
u32 seed = 0) noexcept {
|
||||
// mix in 'a', then mix in 'b'
|
||||
u32 h = fast_hash32(seed ^ a);
|
||||
return fast_hash32(h ^ b);
|
||||
}
|
||||
|
||||
static inline u32 fast_hash64(u64 x) noexcept {
|
||||
u32 x1 = static_cast<u32>(x & 0x00000000FFFFFFFF);
|
||||
u32 x2 = static_cast<u32>(x >> 32);
|
||||
return hash_pair(x1, x2);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functor for hashing arbitrary byte‐ranges to a 32‐bit value
|
||||
//-----------------------------------------------------------------------------
|
||||
// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
|
||||
template <typename T> struct Hash {
|
||||
static_assert(fl::is_pod<T>::value,
|
||||
"fl::Hash<T> only supports POD types (integrals, floats, "
|
||||
"etc.), you need to define your own hash.");
|
||||
u32 operator()(const T &key) const noexcept {
|
||||
return MurmurHash3_x86_32(&key, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct FastHash {
|
||||
static_assert(fl::is_pod<T>::value,
|
||||
"fl::FastHash<T> only supports POD types (integrals, floats, "
|
||||
"etc.), you need to define your own hash.");
|
||||
u32 operator()(const T &key) const noexcept {
|
||||
return fast_hash32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct FastHash<vec2<T>> {
|
||||
u32 operator()(const vec2<T> &key) const noexcept {
|
||||
if (sizeof(T) == sizeof(fl::u8)) {
|
||||
u32 x = static_cast<u32>(key.x) +
|
||||
(static_cast<u32>(key.y) << 8);
|
||||
return fast_hash32(x);
|
||||
}
|
||||
if (sizeof(T) == sizeof(u16)) {
|
||||
u32 x = static_cast<u32>(key.x) +
|
||||
(static_cast<u32>(key.y) << 16);
|
||||
return fast_hash32(x);
|
||||
}
|
||||
if (sizeof(T) == sizeof(u32)) {
|
||||
return hash_pair(key.x, key.y);
|
||||
}
|
||||
return MurmurHash3_x86_32(&key, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct Hash<T *> {
|
||||
u32 operator()(T *key) const noexcept {
|
||||
if (sizeof(T *) == sizeof(u32)) {
|
||||
u32 key_u = reinterpret_cast<fl::uptr>(key);
|
||||
return fast_hash32(key_u);
|
||||
} else {
|
||||
return MurmurHash3_x86_32(key, sizeof(T *));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct Hash<vec2<T>> {
|
||||
u32 operator()(const vec2<T> &key) const noexcept {
|
||||
#ifndef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||
#endif
|
||||
T packed[2] = {key.x, key.y};
|
||||
const void *p = &packed[0];
|
||||
return MurmurHash3_x86_32(p, sizeof(packed));
|
||||
#ifndef __clang__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct Hash<fl::shared_ptr<T>> {
|
||||
u32 operator()(const T &key) const noexcept {
|
||||
auto hasher = Hash<T *>();
|
||||
return hasher(key.get());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct Hash<fl::WeakPtr<T>> {
|
||||
u32 operator()(const fl::WeakPtr<T> &key) const noexcept {
|
||||
fl::uptr val = key.ptr_value();
|
||||
return MurmurHash3_x86_32(&val, sizeof(fl::uptr));
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<float> {
|
||||
u32 operator()(const float key) const noexcept {
|
||||
u32 ikey = fl::bit_cast<u32>(key);
|
||||
return fast_hash32(ikey);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<double> {
|
||||
u32 operator()(const double& key) const noexcept {
|
||||
return MurmurHash3_x86_32(&key, sizeof(double));
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<i32> {
|
||||
u32 operator()(const i32 key) const noexcept {
|
||||
u32 ukey = static_cast<u32>(key);
|
||||
return fast_hash32(ukey);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<bool> {
|
||||
u32 operator()(const bool key) const noexcept {
|
||||
return fast_hash32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<fl::u8> {
|
||||
u32 operator()(const fl::u8 &key) const noexcept {
|
||||
return fast_hash32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<u16> {
|
||||
u32 operator()(const u16 &key) const noexcept {
|
||||
return fast_hash32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<u32> {
|
||||
u32 operator()(const u32 &key) const noexcept {
|
||||
return fast_hash32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<i8> {
|
||||
u32 operator()(const i8 &key) const noexcept {
|
||||
u8 v = static_cast<u8>(key);
|
||||
return fast_hash32(v);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct Hash<i16> {
|
||||
u32 operator()(const i16 &key) const noexcept {
|
||||
u16 ukey = static_cast<u16>(key);
|
||||
return fast_hash32(ukey);
|
||||
}
|
||||
};
|
||||
|
||||
// FASTLED_DEFINE_FAST_HASH(fl::u8)
|
||||
// FASTLED_DEFINE_FAST_HASH(u16)
|
||||
// FASTLED_DEFINE_FAST_HASH(u32)
|
||||
// FASTLED_DEFINE_FAST_HASH(i8)
|
||||
// FASTLED_DEFINE_FAST_HASH(i16)
|
||||
// FASTLED_DEFINE_FAST_HASH(bool)
|
||||
|
||||
// FASTLED_DEFINE_FAST_HASH(int)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Convenience for std::string → u32
|
||||
//----------------------------------------------------------------------------
|
||||
template <> struct Hash<fl::string> {
|
||||
u32 operator()(const fl::string &key) const noexcept {
|
||||
return MurmurHash3_x86_32(key.data(), key.size());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
725
libraries/FastLED/src/fl/hash_map.h
Normal file
725
libraries/FastLED/src/fl/hash_map.h
Normal file
@@ -0,0 +1,725 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
HashMap that is optimized for embedded devices. The hashmap
|
||||
will store upto N elements inline, and will spill over to a heap.
|
||||
This hashmap will try not to grow by detecting during rehash that
|
||||
the number of tombstones is greater than the number of elements.
|
||||
This will keep the memory from growing during multiple inserts
|
||||
and removals.
|
||||
*/
|
||||
|
||||
// #include <cstddef>
|
||||
// #include <iterator>
|
||||
|
||||
#include "fl/assert.h"
|
||||
#include "fl/bitset.h"
|
||||
#include "fl/clamp.h"
|
||||
#include "fl/hash.h"
|
||||
#include "fl/map_range.h"
|
||||
#include "fl/optional.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/align.h"
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/math_macros.h"
|
||||
|
||||
#include "fl/compiler_control.h"
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_SHORTEN_64_TO_32
|
||||
|
||||
namespace fl {
|
||||
|
||||
// // begin using declarations for stl compatibility
|
||||
// use fl::equal_to;
|
||||
// use fl::hash_map;
|
||||
// use fl::unordered_map;
|
||||
// // end using declarations for stl compatibility
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
template <typename T> struct EqualTo {
|
||||
bool operator()(const T &a, const T &b) const { return a == b; }
|
||||
};
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
// -- HashMap class
|
||||
// ------------------------------------------------------------- Begin HashMap
|
||||
// class
|
||||
|
||||
#ifndef FASTLED_HASHMAP_INLINED_COUNT
|
||||
#define FASTLED_HASHMAP_INLINED_COUNT 8
|
||||
#endif
|
||||
|
||||
template <typename Key, typename T, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = EqualTo<Key>,
|
||||
int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
|
||||
class FL_ALIGN HashMap {
|
||||
public:
|
||||
HashMap() : HashMap(FASTLED_HASHMAP_INLINED_COUNT, 0.7f) {}
|
||||
HashMap(fl::size initial_capacity) : HashMap(initial_capacity, 0.7f) {}
|
||||
|
||||
HashMap(fl::size initial_capacity, float max_load)
|
||||
: _buckets(next_power_of_two(initial_capacity)), _size(0),
|
||||
_tombstones(0), _occupied(next_power_of_two(initial_capacity)),
|
||||
_deleted(next_power_of_two(initial_capacity)) {
|
||||
setLoadFactor(max_load);
|
||||
}
|
||||
|
||||
void setLoadFactor(float f) {
|
||||
f = fl::clamp(f, 0.f, 1.f);
|
||||
mLoadFactor = fl::map_range<float, u8>(f, 0.f, 1.f, 0, 255);
|
||||
}
|
||||
|
||||
// Iterator support.
|
||||
struct iterator {
|
||||
// Standard iterator typedefs
|
||||
// using difference_type = std::ptrdiff_t;
|
||||
using value_type = pair<const Key, T>; // Keep const Key as per standard
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
// using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
iterator() : _map(nullptr), _idx(0) {}
|
||||
iterator(HashMap *m, fl::size idx) : _map(m), _idx(idx) {
|
||||
advance_to_occupied();
|
||||
}
|
||||
|
||||
value_type operator*() const {
|
||||
auto &e = _map->_buckets[_idx];
|
||||
return {e.key, e.value};
|
||||
}
|
||||
|
||||
pointer operator->() {
|
||||
// Use reinterpret_cast since pair<const Key, T> and pair<Key, T> are different types
|
||||
// but have the same memory layout, then destroy/reconstruct to avoid assignment issues
|
||||
using mutable_value_type = pair<Key, T>;
|
||||
auto& mutable_cached = *reinterpret_cast<mutable_value_type*>(&_cached_value);
|
||||
mutable_cached.~mutable_value_type();
|
||||
new (&mutable_cached) mutable_value_type(operator*());
|
||||
return &_cached_value;
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
// Use reinterpret_cast since pair<const Key, T> and pair<Key, T> are different types
|
||||
// but have the same memory layout, then destroy/reconstruct to avoid assignment issues
|
||||
using mutable_value_type = pair<Key, T>;
|
||||
auto& mutable_cached = *fl::bit_cast<mutable_value_type*>(&_cached_value);
|
||||
mutable_cached.~mutable_value_type();
|
||||
new (&mutable_cached) mutable_value_type(operator*());
|
||||
return &_cached_value;
|
||||
}
|
||||
|
||||
iterator &operator++() {
|
||||
++_idx;
|
||||
advance_to_occupied();
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &o) const {
|
||||
return _map == o._map && _idx == o._idx;
|
||||
}
|
||||
bool operator!=(const iterator &o) const { return !(*this == o); }
|
||||
|
||||
void advance_to_occupied() {
|
||||
if (!_map)
|
||||
return;
|
||||
fl::size cap = _map->_buckets.size();
|
||||
while (_idx < cap && !_map->is_occupied(_idx))
|
||||
++_idx;
|
||||
}
|
||||
|
||||
private:
|
||||
HashMap *_map;
|
||||
fl::size _idx;
|
||||
mutable value_type _cached_value;
|
||||
friend class HashMap;
|
||||
};
|
||||
|
||||
struct const_iterator {
|
||||
// Standard iterator typedefs
|
||||
// using difference_type = std::ptrdiff_t;
|
||||
using value_type = pair<const Key, T>;
|
||||
using pointer = const value_type *;
|
||||
using reference = const value_type &;
|
||||
// using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator() : _map(nullptr), _idx(0) {}
|
||||
const_iterator(const HashMap *m, fl::size idx) : _map(m), _idx(idx) {
|
||||
advance_to_occupied();
|
||||
}
|
||||
const_iterator(const iterator &it) : _map(it._map), _idx(it._idx) {}
|
||||
|
||||
value_type operator*() const {
|
||||
auto &e = _map->_buckets[_idx];
|
||||
return {e.key, e.value};
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
// Use reinterpret_cast since pair<const Key, T> and pair<Key, T> are different types
|
||||
// but have the same memory layout, then destroy/reconstruct to avoid assignment issues
|
||||
using mutable_value_type = pair<Key, T>;
|
||||
auto& mutable_cached = *reinterpret_cast<mutable_value_type*>(&_cached_value);
|
||||
mutable_cached.~mutable_value_type();
|
||||
new (&mutable_cached) mutable_value_type(operator*());
|
||||
return &_cached_value;
|
||||
}
|
||||
|
||||
const_iterator &operator++() {
|
||||
++_idx;
|
||||
advance_to_occupied();
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator++(int) {
|
||||
const_iterator tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator &o) const {
|
||||
return _map == o._map && _idx == o._idx;
|
||||
}
|
||||
bool operator!=(const const_iterator &o) const { return !(*this == o); }
|
||||
|
||||
void advance_to_occupied() {
|
||||
if (!_map)
|
||||
return;
|
||||
fl::size cap = _map->_buckets.size();
|
||||
while (_idx < cap && !_map->is_occupied(_idx))
|
||||
++_idx;
|
||||
}
|
||||
|
||||
friend class HashMap;
|
||||
|
||||
private:
|
||||
const HashMap *_map;
|
||||
fl::size _idx;
|
||||
mutable value_type _cached_value;
|
||||
};
|
||||
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
iterator end() { return iterator(this, _buckets.size()); }
|
||||
const_iterator begin() const { return const_iterator(this, 0); }
|
||||
const_iterator end() const { return const_iterator(this, _buckets.size()); }
|
||||
const_iterator cbegin() const { return const_iterator(this, 0); }
|
||||
const_iterator cend() const {
|
||||
return const_iterator(this, _buckets.size());
|
||||
}
|
||||
|
||||
static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones,
|
||||
u8 load_factor) {
|
||||
// (size + tombstones) << 8 : multiply numerator by 256
|
||||
// capacity * max_load : denominator * threshold
|
||||
u32 lhs = (size + tombstones) << 8;
|
||||
u32 rhs = (bucket_size * load_factor);
|
||||
return lhs > rhs;
|
||||
}
|
||||
|
||||
// returns true if (size + tombs)/capacity > _max_load/256
|
||||
bool needs_rehash() const {
|
||||
return NeedsRehash(_size, _buckets.size(), _tombstones, mLoadFactor);
|
||||
}
|
||||
|
||||
// insert or overwrite
|
||||
void insert(const Key &key, const T &value) {
|
||||
const bool will_rehash = needs_rehash();
|
||||
if (will_rehash) {
|
||||
// if half the buckets are tombstones, rehash inline to prevent
|
||||
// memory spill over into the heap.
|
||||
if (_tombstones > _size) {
|
||||
rehash_inline_no_resize();
|
||||
} else {
|
||||
rehash(_buckets.size() * 2);
|
||||
}
|
||||
}
|
||||
fl::size idx;
|
||||
bool is_new;
|
||||
fl::pair<fl::size, bool> p = find_slot(key);
|
||||
idx = p.first;
|
||||
is_new = p.second;
|
||||
if (is_new) {
|
||||
_buckets[idx].key = key;
|
||||
_buckets[idx].value = value;
|
||||
mark_occupied(idx);
|
||||
++_size;
|
||||
} else {
|
||||
FASTLED_ASSERT(idx != npos(), "HashMap::insert: invalid index at "
|
||||
<< idx << " which is " << npos());
|
||||
_buckets[idx].value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Move version of insert
|
||||
void insert(Key &&key, T &&value) {
|
||||
const bool will_rehash = needs_rehash();
|
||||
if (will_rehash) {
|
||||
// if half the buckets are tombstones, rehash inline to prevent
|
||||
// memory spill over into the heap.
|
||||
if (_tombstones > _size) {
|
||||
rehash_inline_no_resize();
|
||||
} else {
|
||||
rehash(_buckets.size() * 2);
|
||||
}
|
||||
}
|
||||
fl::size idx;
|
||||
bool is_new;
|
||||
fl::pair<fl::size, bool> p = find_slot(key);
|
||||
idx = p.first;
|
||||
is_new = p.second;
|
||||
if (is_new) {
|
||||
_buckets[idx].key = fl::move(key);
|
||||
_buckets[idx].value = fl::move(value);
|
||||
mark_occupied(idx);
|
||||
++_size;
|
||||
} else {
|
||||
FASTLED_ASSERT(idx != npos(), "HashMap::insert: invalid index at "
|
||||
<< idx << " which is " << npos());
|
||||
_buckets[idx].value = fl::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
// remove key; returns true if removed
|
||||
bool remove(const Key &key) {
|
||||
auto idx = find_index(key);
|
||||
if (idx == npos())
|
||||
return false;
|
||||
mark_deleted(idx);
|
||||
--_size;
|
||||
++_tombstones;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool erase(const Key &key) { return remove(key); }
|
||||
|
||||
// Iterator-based erase - more efficient when you already have the iterator position
|
||||
iterator erase(iterator it) {
|
||||
if (it == end() || it._map != this) {
|
||||
return end(); // Invalid iterator
|
||||
}
|
||||
|
||||
// Mark the current position as deleted
|
||||
mark_deleted(it._idx);
|
||||
--_size;
|
||||
++_tombstones;
|
||||
|
||||
// Advance to next valid element and return iterator to it
|
||||
++it._idx;
|
||||
it.advance_to_occupied();
|
||||
return it;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_buckets.assign(_buckets.size(), Entry{});
|
||||
_occupied.reset();
|
||||
_deleted.reset();
|
||||
_size = _tombstones = 0;
|
||||
}
|
||||
|
||||
// find pointer to value or nullptr
|
||||
T *find_value(const Key &key) {
|
||||
auto idx = find_index(key);
|
||||
return idx == npos() ? nullptr : &_buckets[idx].value;
|
||||
}
|
||||
|
||||
const T *find_value(const Key &key) const {
|
||||
auto idx = find_index(key);
|
||||
return idx == npos() ? nullptr : &_buckets[idx].value;
|
||||
}
|
||||
|
||||
iterator find(const Key &key) {
|
||||
auto idx = find_index(key);
|
||||
return idx == npos() ? end() : iterator(this, idx);
|
||||
}
|
||||
|
||||
const_iterator find(const Key &key) const {
|
||||
auto idx = find_index(key);
|
||||
return idx == npos() ? end() : const_iterator(this, idx);
|
||||
}
|
||||
|
||||
bool contains(const Key &key) const {
|
||||
auto idx = find_index(key);
|
||||
return idx != npos();
|
||||
}
|
||||
|
||||
// access or default-construct
|
||||
T &operator[](const Key &key) {
|
||||
fl::size idx;
|
||||
bool is_new;
|
||||
|
||||
fl::pair<fl::size, bool> p = find_slot(key);
|
||||
idx = p.first;
|
||||
is_new = p.second;
|
||||
|
||||
// Check if find_slot failed to find a valid slot (HashMap is full)
|
||||
if (idx == npos()) {
|
||||
// Need to resize to make room
|
||||
if (needs_rehash()) {
|
||||
// if half the buckets are tombstones, rehash inline to prevent
|
||||
// memory growth. Otherwise, double the size.
|
||||
if (_tombstones >= _buckets.size() / 2) {
|
||||
rehash_inline_no_resize();
|
||||
} else {
|
||||
rehash(_buckets.size() * 2);
|
||||
}
|
||||
} else {
|
||||
// Force a rehash with double size if needs_rehash() doesn't detect the issue
|
||||
rehash(_buckets.size() * 2);
|
||||
}
|
||||
|
||||
// Try find_slot again after resize
|
||||
p = find_slot(key);
|
||||
idx = p.first;
|
||||
is_new = p.second;
|
||||
|
||||
// If still npos() after resize, something is seriously wrong
|
||||
if (idx == npos()) {
|
||||
// This should never happen after a successful resize
|
||||
static T default_value{};
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new) {
|
||||
_buckets[idx].key = key;
|
||||
_buckets[idx].value = T{};
|
||||
mark_occupied(idx);
|
||||
++_size;
|
||||
}
|
||||
return _buckets[idx].value;
|
||||
}
|
||||
|
||||
fl::size size() const { return _size; }
|
||||
bool empty() const { return _size == 0; }
|
||||
fl::size capacity() const { return _buckets.size(); }
|
||||
|
||||
|
||||
|
||||
private:
|
||||
static fl::size npos() {
|
||||
return static_cast<fl::size>(-1);
|
||||
}
|
||||
|
||||
// Helper methods to check entry state
|
||||
bool is_occupied(fl::size idx) const { return _occupied.test(idx); }
|
||||
|
||||
bool is_deleted(fl::size idx) const { return _deleted.test(idx); }
|
||||
|
||||
bool is_empty(fl::size idx) const {
|
||||
return !is_occupied(idx) && !is_deleted(idx);
|
||||
}
|
||||
|
||||
void mark_occupied(fl::size idx) {
|
||||
_occupied.set(idx);
|
||||
_deleted.reset(idx);
|
||||
}
|
||||
|
||||
void mark_deleted(fl::size idx) {
|
||||
_occupied.reset(idx);
|
||||
_deleted.set(idx);
|
||||
}
|
||||
|
||||
void mark_empty(fl::size idx) {
|
||||
_occupied.reset(idx);
|
||||
_deleted.reset(idx);
|
||||
}
|
||||
|
||||
struct alignas(fl::max_align<Key, T>::value) Entry {
|
||||
Key key;
|
||||
T value;
|
||||
void swap(Entry &other) {
|
||||
fl::swap(key, other.key);
|
||||
fl::swap(value, other.value);
|
||||
}
|
||||
};
|
||||
|
||||
static fl::size next_power_of_two(fl::size n) {
|
||||
fl::size p = 1;
|
||||
while (p < n)
|
||||
p <<= 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
pair<fl::size, bool> find_slot(const Key &key) const {
|
||||
const fl::size cap = _buckets.size();
|
||||
const fl::size mask = cap - 1;
|
||||
const fl::size h = _hash(key) & mask;
|
||||
fl::size first_tomb = npos();
|
||||
|
||||
if (cap <= 8) {
|
||||
// linear probing
|
||||
for (fl::size i = 0; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
|
||||
if (is_empty(idx))
|
||||
return {first_tomb != npos() ? first_tomb : idx, true};
|
||||
if (is_deleted(idx)) {
|
||||
if (first_tomb == npos())
|
||||
first_tomb = idx;
|
||||
} else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
|
||||
return {idx, false};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// quadratic probing up to 8 tries
|
||||
fl::size i = 0;
|
||||
for (; i < 8; ++i) {
|
||||
const fl::size idx = (h + i + i * i) & mask;
|
||||
|
||||
if (is_empty(idx))
|
||||
return {first_tomb != npos() ? first_tomb : idx, true};
|
||||
if (is_deleted(idx)) {
|
||||
if (first_tomb == npos())
|
||||
first_tomb = idx;
|
||||
} else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
|
||||
return {idx, false};
|
||||
}
|
||||
}
|
||||
// fallback to linear for the rest
|
||||
for (; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
|
||||
if (is_empty(idx))
|
||||
return {first_tomb != npos() ? first_tomb : idx, true};
|
||||
if (is_deleted(idx)) {
|
||||
if (first_tomb == npos())
|
||||
first_tomb = idx;
|
||||
} else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
|
||||
return {idx, false};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {npos(), false};
|
||||
}
|
||||
|
||||
enum {
|
||||
kLinearProbingOnlySize = 8,
|
||||
kQuadraticProbingTries = 8,
|
||||
};
|
||||
|
||||
fl::size find_index(const Key &key) const {
|
||||
const fl::size cap = _buckets.size();
|
||||
const fl::size mask = cap - 1;
|
||||
const fl::size h = _hash(key) & mask;
|
||||
|
||||
if (cap <= kLinearProbingOnlySize) {
|
||||
// linear probing
|
||||
for (fl::size i = 0; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
if (is_empty(idx))
|
||||
return npos();
|
||||
if (is_occupied(idx) && _equal(_buckets[idx].key, key))
|
||||
return idx;
|
||||
}
|
||||
} else {
|
||||
// quadratic probing up to 8 tries
|
||||
fl::size i = 0;
|
||||
for (; i < kQuadraticProbingTries; ++i) {
|
||||
const fl::size idx = (h + i + i * i) & mask;
|
||||
if (is_empty(idx))
|
||||
return npos();
|
||||
if (is_occupied(idx) && _equal(_buckets[idx].key, key))
|
||||
return idx;
|
||||
}
|
||||
// fallback to linear for the rest
|
||||
for (; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
if (is_empty(idx))
|
||||
return npos();
|
||||
if (is_occupied(idx) && _equal(_buckets[idx].key, key))
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
return npos();
|
||||
}
|
||||
|
||||
fl::size find_unoccupied_index_using_bitset(
|
||||
const Key &key, const fl::bitset<1024> &occupied_set) const {
|
||||
const fl::size cap = _buckets.size();
|
||||
const fl::size mask = cap - 1;
|
||||
const fl::size h = _hash(key) & mask;
|
||||
|
||||
if (cap <= kLinearProbingOnlySize) {
|
||||
// linear probing
|
||||
for (fl::size i = 0; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
bool occupied = occupied_set.test(idx);
|
||||
if (occupied) {
|
||||
continue;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
} else {
|
||||
// quadratic probing up to 8 tries
|
||||
fl::size i = 0;
|
||||
for (; i < kQuadraticProbingTries; ++i) {
|
||||
const fl::size idx = (h + i + i * i) & mask;
|
||||
bool occupied = occupied_set.test(idx);
|
||||
if (occupied) {
|
||||
continue;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
// fallback to linear for the rest
|
||||
for (; i < cap; ++i) {
|
||||
const fl::size idx = (h + i) & mask;
|
||||
bool occupied = occupied_set.test(idx);
|
||||
if (occupied) {
|
||||
continue;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return npos();
|
||||
}
|
||||
|
||||
void rehash(fl::size new_cap) {
|
||||
new_cap = next_power_of_two(new_cap);
|
||||
fl::vector_inlined<Entry, INLINED_COUNT> old;
|
||||
fl::bitset<1024> old_occupied = _occupied;
|
||||
|
||||
_buckets.swap(old);
|
||||
_buckets.clear();
|
||||
_buckets.assign(new_cap, Entry{});
|
||||
|
||||
_occupied.reset();
|
||||
_occupied.resize(new_cap);
|
||||
_deleted.reset();
|
||||
_deleted.resize(new_cap);
|
||||
|
||||
_size = _tombstones = 0;
|
||||
|
||||
for (fl::size i = 0; i < old.size(); i++) {
|
||||
if (old_occupied.test(i))
|
||||
insert(fl::move(old[i].key), fl::move(old[i].value));
|
||||
}
|
||||
}
|
||||
|
||||
// Rehash the inline buckets without resizing
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
void rehash_inline_no_resize() {
|
||||
// filter out tombstones and compact
|
||||
fl::size cap = _buckets.size();
|
||||
fl::size pos = 0;
|
||||
// compact live elements to the left.
|
||||
for (fl::size i = 0; i < cap; ++i) {
|
||||
if (is_occupied(i)) {
|
||||
if (pos != i) {
|
||||
_buckets[pos] = _buckets[i];
|
||||
mark_empty(i);
|
||||
mark_occupied(pos);
|
||||
}
|
||||
++pos;
|
||||
} else if (is_deleted(i)) {
|
||||
mark_empty(i);
|
||||
}
|
||||
}
|
||||
|
||||
fl::bitset<1024> occupied; // Preallocate a bitset of size 1024
|
||||
// swap the components, this will happen at most N times,
|
||||
// use the occupied bitset to track which entries are occupied
|
||||
// in the array rather than just copied in.
|
||||
fl::optional<Entry> tmp;
|
||||
for (fl::size i = 0; i < _size; ++i) {
|
||||
const bool already_finished = occupied.test(i);
|
||||
if (already_finished) {
|
||||
continue;
|
||||
}
|
||||
auto &e = _buckets[i];
|
||||
// FASTLED_ASSERT(e.state == EntryState::Occupied,
|
||||
// "HashMap::rehash_inline_no_resize: invalid
|
||||
// state");
|
||||
|
||||
fl::size idx = find_unoccupied_index_using_bitset(e.key, occupied);
|
||||
if (idx == npos()) {
|
||||
// no more space
|
||||
FASTLED_ASSERT(
|
||||
false, "HashMap::rehash_inline_no_resize: invalid index at "
|
||||
<< idx << " which is " << npos());
|
||||
return;
|
||||
}
|
||||
// if idx < pos then we are moving the entry to a new location
|
||||
FASTLED_ASSERT(!tmp,
|
||||
"HashMap::rehash_inline_no_resize: invalid tmp");
|
||||
if (idx >= _size) {
|
||||
// directly copy it now
|
||||
_buckets[idx] = e;
|
||||
continue;
|
||||
}
|
||||
tmp = e;
|
||||
occupied.set(idx);
|
||||
_buckets[idx] = *tmp.ptr();
|
||||
while (!tmp.empty()) {
|
||||
// we have to find a place for temp.
|
||||
// find new position for tmp.
|
||||
auto key = tmp.ptr()->key;
|
||||
fl::size new_idx =
|
||||
find_unoccupied_index_using_bitset(key, occupied);
|
||||
if (new_idx == npos()) {
|
||||
// no more space
|
||||
FASTLED_ASSERT(
|
||||
false,
|
||||
"HashMap::rehash_inline_no_resize: invalid index at "
|
||||
<< new_idx << " which is " << npos());
|
||||
return;
|
||||
}
|
||||
occupied.set(new_idx);
|
||||
if (new_idx < _size) {
|
||||
// we have to swap the entry at new_idx with tmp
|
||||
fl::optional<Entry> tmp2 = _buckets[new_idx];
|
||||
_buckets[new_idx] = *tmp.ptr();
|
||||
tmp = tmp2;
|
||||
} else {
|
||||
// we can just move tmp to new_idx
|
||||
_buckets[new_idx] = *tmp.ptr();
|
||||
tmp.reset();
|
||||
}
|
||||
}
|
||||
FASTLED_ASSERT(
|
||||
occupied.test(i),
|
||||
"HashMap::rehash_inline_no_resize: invalid occupied at " << i);
|
||||
FASTLED_ASSERT(
|
||||
tmp.empty(), "HashMap::rehash_inline_no_resize: invalid tmp at " << i);
|
||||
}
|
||||
// Reset tombstones count since we've cleared all deleted entries
|
||||
_tombstones = 0;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
fl::vector_inlined<Entry, INLINED_COUNT> _buckets;
|
||||
fl::size _size;
|
||||
fl::size _tombstones;
|
||||
u8 mLoadFactor;
|
||||
fl::bitset<1024> _occupied;
|
||||
fl::bitset<1024> _deleted;
|
||||
Hash _hash;
|
||||
KeyEqual _equal;
|
||||
};
|
||||
|
||||
// begin using declarations for stl compatibility
|
||||
template <typename T> using equal_to = EqualTo<T>;
|
||||
|
||||
template <typename Key, typename T, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = equal_to<Key>>
|
||||
using hash_map = HashMap<Key, T, Hash, KeyEqual>;
|
||||
|
||||
template <typename Key, typename T, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = equal_to<Key>>
|
||||
using unordered_map = HashMap<Key, T, Hash, KeyEqual>;
|
||||
// end using declarations for stl compatibility
|
||||
|
||||
} // namespace fl
|
||||
|
||||
FL_DISABLE_WARNING_POP
|
||||
162
libraries/FastLED/src/fl/hash_map_lru.h
Normal file
162
libraries/FastLED/src/fl/hash_map_lru.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
LRU (Least Recently Used) HashMap that is optimized for embedded devices.
|
||||
This hashmap has a maximum size and will automatically evict the least
|
||||
recently used items when it reaches capacity.
|
||||
*/
|
||||
|
||||
#include "fl/hash_map.h"
|
||||
#include "fl/type_traits.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename Key, typename T, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = EqualTo<Key>,
|
||||
int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
|
||||
class HashMapLru {
|
||||
private:
|
||||
// Wrapper for values that includes access time tracking
|
||||
struct ValueWithTimestamp {
|
||||
T value;
|
||||
u32 last_access_time;
|
||||
|
||||
ValueWithTimestamp() : last_access_time(0) {}
|
||||
ValueWithTimestamp(const T &v, u32 time)
|
||||
: value(v), last_access_time(time) {}
|
||||
};
|
||||
|
||||
public:
|
||||
HashMapLru(fl::size max_size) : mMaxSize(max_size), mCurrentTime(0) {
|
||||
// Ensure max size is at least 1
|
||||
if (mMaxSize < 1)
|
||||
mMaxSize = 1;
|
||||
}
|
||||
|
||||
void setMaxSize(fl::size max_size) {
|
||||
while (mMaxSize < max_size) {
|
||||
// Evict oldest items until we reach the new max size
|
||||
evictOldest();
|
||||
}
|
||||
mMaxSize = max_size;
|
||||
}
|
||||
|
||||
void swap(HashMapLru &other) {
|
||||
fl::swap(mMap, other.mMap);
|
||||
fl::swap(mMaxSize, other.mMaxSize);
|
||||
fl::swap(mCurrentTime, other.mCurrentTime);
|
||||
}
|
||||
|
||||
// Insert or update a key-value pair
|
||||
void insert(const Key &key, const T &value) {
|
||||
// Only evict if we're at capacity AND this is a new key
|
||||
const ValueWithTimestamp *existing = mMap.find_value(key);
|
||||
|
||||
auto curr = mCurrentTime++;
|
||||
|
||||
if (existing) {
|
||||
// Update the value and access time
|
||||
ValueWithTimestamp &vwt =
|
||||
const_cast<ValueWithTimestamp &>(*existing);
|
||||
vwt.value = value;
|
||||
vwt.last_access_time = curr;
|
||||
return;
|
||||
}
|
||||
if (mMap.size() >= mMaxSize) {
|
||||
evictOldest();
|
||||
}
|
||||
|
||||
// Insert or update the value with current timestamp
|
||||
ValueWithTimestamp vwt(value, mCurrentTime);
|
||||
mMap.insert(key, vwt);
|
||||
}
|
||||
|
||||
// Get value for key, returns nullptr if not found
|
||||
T *find_value(const Key &key) {
|
||||
ValueWithTimestamp *vwt = mMap.find_value(key);
|
||||
if (vwt) {
|
||||
// Update access time
|
||||
auto curr = mCurrentTime++;
|
||||
vwt->last_access_time = curr;
|
||||
return &vwt->value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get value for key, returns nullptr if not found (const version)
|
||||
const T *find_value(const Key &key) const {
|
||||
const ValueWithTimestamp *vwt = mMap.find_value(key);
|
||||
return vwt ? &vwt->value : nullptr;
|
||||
}
|
||||
|
||||
// Access operator - creates entry if not exists
|
||||
T &operator[](const Key &key) {
|
||||
// If we're at capacity and this is a new key, evict oldest
|
||||
auto curr = mCurrentTime++;
|
||||
|
||||
auto entry = mMap.find_value(key);
|
||||
if (entry) {
|
||||
// Update access time
|
||||
entry->last_access_time = curr;
|
||||
return entry->value;
|
||||
}
|
||||
|
||||
if (mMap.size() >= mMaxSize) {
|
||||
evictOldest();
|
||||
}
|
||||
|
||||
// Get or create entry and update timestamp
|
||||
// mCurrentTime++;
|
||||
ValueWithTimestamp &vwt = mMap[key];
|
||||
vwt.last_access_time = curr;
|
||||
return vwt.value;
|
||||
}
|
||||
|
||||
// Remove a key
|
||||
bool remove(const Key &key) { return mMap.remove(key); }
|
||||
|
||||
// Clear the map
|
||||
void clear() {
|
||||
mMap.clear();
|
||||
mCurrentTime = 0;
|
||||
}
|
||||
|
||||
// Size accessors
|
||||
fl::size size() const { return mMap.size(); }
|
||||
bool empty() const { return mMap.empty(); }
|
||||
fl::size capacity() const { return mMaxSize; }
|
||||
|
||||
private:
|
||||
// Evict the least recently used item
|
||||
void evictOldest() {
|
||||
if (mMap.empty())
|
||||
return;
|
||||
|
||||
// Find the entry with the oldest timestamp
|
||||
Key oldest_key;
|
||||
u32 oldest_time = UINT32_MAX;
|
||||
bool found = false;
|
||||
|
||||
for (auto it = mMap.begin(); it != mMap.end(); ++it) {
|
||||
const auto &entry = *it;
|
||||
const ValueWithTimestamp &vwt = entry.second;
|
||||
|
||||
if (vwt.last_access_time < oldest_time) {
|
||||
oldest_time = vwt.last_access_time;
|
||||
oldest_key = entry.first;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the oldest entry
|
||||
if (found) {
|
||||
mMap.remove(oldest_key);
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<Key, ValueWithTimestamp, Hash, KeyEqual, INLINED_COUNT> mMap;
|
||||
fl::size mMaxSize;
|
||||
u32 mCurrentTime;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
32
libraries/FastLED/src/fl/hash_set.h
Normal file
32
libraries/FastLED/src/fl/hash_set.h
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/hash_map.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// just define a hashset to be a hashmap with a dummy value
|
||||
|
||||
template <typename Key, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = EqualTo<Key>>
|
||||
class HashSet : public HashMap<Key, bool, Hash, KeyEqual> {
|
||||
public:
|
||||
using Base = HashMap<Key, bool, Hash, KeyEqual>;
|
||||
using iterator = typename Base::iterator;
|
||||
using const_iterator = typename Base::const_iterator;
|
||||
|
||||
HashSet(fl::size initial_capacity = 8, float max_load = 0.7f)
|
||||
: Base(initial_capacity, max_load) {}
|
||||
|
||||
void insert(const Key &key) { Base::insert(key, true); }
|
||||
|
||||
void erase(const Key &key) { Base::erase(key); }
|
||||
|
||||
iterator find(const Key &key) { return Base::find(key); }
|
||||
};
|
||||
|
||||
template <typename Key, typename Hash = Hash<Key>,
|
||||
typename KeyEqual = EqualTo<Key>>
|
||||
using hash_set = HashSet<Key, Hash, KeyEqual>;
|
||||
|
||||
} // namespace fl
|
||||
109
libraries/FastLED/src/fl/hsv.h
Normal file
109
libraries/FastLED/src/fl/hsv.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/// @file hsv.h
|
||||
/// Defines the hue, saturation, and value (HSV) pixel struct
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @addtogroup PixelTypes Pixel Data Types (CRGB/CHSV)
|
||||
/// @{
|
||||
|
||||
/// Representation of an HSV pixel (hue, saturation, value (aka brightness)).
|
||||
struct CHSV {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
/// Color hue.
|
||||
/// This is an 8-bit value representing an angle around
|
||||
/// the color wheel. Where 0 is 0°, and 255 is 358°.
|
||||
fl::u8 hue;
|
||||
fl::u8 h; ///< @copydoc hue
|
||||
};
|
||||
union {
|
||||
/// Color saturation.
|
||||
/// This is an 8-bit value representing a percentage.
|
||||
fl::u8 saturation;
|
||||
fl::u8 sat; ///< @copydoc saturation
|
||||
fl::u8 s; ///< @copydoc saturation
|
||||
};
|
||||
union {
|
||||
/// Color value (brightness).
|
||||
/// This is an 8-bit value representing a percentage.
|
||||
fl::u8 value;
|
||||
fl::u8 val; ///< @copydoc value
|
||||
fl::u8 v; ///< @copydoc value
|
||||
};
|
||||
};
|
||||
/// Access the hue, saturation, and value data as an array.
|
||||
/// Where:
|
||||
/// * `raw[0]` is the hue
|
||||
/// * `raw[1]` is the saturation
|
||||
/// * `raw[2]` is the value
|
||||
fl::u8 raw[3];
|
||||
};
|
||||
|
||||
/// Array access operator to index into the CHSV object
|
||||
/// @param x the index to retrieve (0-2)
|
||||
/// @returns the CHSV::raw value for the given index
|
||||
inline fl::u8& operator[] (fl::u8 x) __attribute__((always_inline))
|
||||
{
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
/// @copydoc operator[]
|
||||
inline const fl::u8& operator[] (fl::u8 x) const __attribute__((always_inline))
|
||||
{
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
/// Default constructor
|
||||
/// @warning Default values are UNITIALIZED!
|
||||
constexpr inline CHSV() __attribute__((always_inline)): h(0), s(0), v(0) { }
|
||||
|
||||
/// Allow construction from hue, saturation, and value
|
||||
/// @param ih input hue
|
||||
/// @param is input saturation
|
||||
/// @param iv input value
|
||||
constexpr inline CHSV( fl::u8 ih, fl::u8 is, fl::u8 iv) __attribute__((always_inline))
|
||||
: h(ih), s(is), v(iv)
|
||||
{
|
||||
}
|
||||
|
||||
/// Allow copy construction
|
||||
constexpr inline CHSV(const CHSV& rhs) noexcept : h(rhs.h), s(rhs.s), v(rhs.v) { }
|
||||
|
||||
/// Allow copy construction
|
||||
inline CHSV& operator= (const CHSV& rhs) __attribute__((always_inline)) = default;
|
||||
|
||||
/// Assign new HSV values
|
||||
/// @param ih input hue
|
||||
/// @param is input saturation
|
||||
/// @param iv input value
|
||||
/// @returns reference to the CHSV object
|
||||
inline CHSV& setHSV(fl::u8 ih, fl::u8 is, fl::u8 iv) __attribute__((always_inline))
|
||||
{
|
||||
h = ih;
|
||||
s = is;
|
||||
v = iv;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Pre-defined hue values for CHSV objects
|
||||
typedef enum {
|
||||
HUE_RED = 0, ///< Red (0°)
|
||||
HUE_ORANGE = 32, ///< Orange (45°)
|
||||
HUE_YELLOW = 64, ///< Yellow (90°)
|
||||
HUE_GREEN = 96, ///< Green (135°)
|
||||
HUE_AQUA = 128, ///< Aqua (180°)
|
||||
HUE_BLUE = 160, ///< Blue (225°)
|
||||
HUE_PURPLE = 192, ///< Purple (270°)
|
||||
HUE_PINK = 224 ///< Pink (315°)
|
||||
} HSVHue;
|
||||
|
||||
/// @} PixelTypes
|
||||
|
||||
} // namespace fl
|
||||
197
libraries/FastLED/src/fl/hsv16.cpp
Normal file
197
libraries/FastLED/src/fl/hsv16.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
#include "fl/hsv16.h"
|
||||
#include "fl/math.h"
|
||||
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/ease.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Improved 8-bit to 16-bit scaling using the same technique as map8_to_16
|
||||
// but with proper rounding for the 0-255 to 0-65535 conversion
|
||||
static inline u16 scale8_to_16_accurate(u8 x) {
|
||||
if (x == 0) return 0;
|
||||
if (x == 255) return 65535;
|
||||
// Use 32-bit arithmetic with rounding: (x * 65535 + 127) / 255
|
||||
// This is equivalent to: (x * 65535 + 255/2) / 255
|
||||
return (u16)(((u32)x * 65535 + 127) / 255);
|
||||
}
|
||||
|
||||
static HSV16 RGBtoHSV16(const CRGB &rgb) {
|
||||
// Work with 8-bit values directly
|
||||
u8 r = rgb.r;
|
||||
u8 g = rgb.g;
|
||||
u8 b = rgb.b;
|
||||
|
||||
// Find min and max
|
||||
u8 mx = fl_max(r, fl_max(g, b));
|
||||
u8 mn = fl_min(r, fl_min(g, b));
|
||||
u8 delta = mx - mn;
|
||||
|
||||
u16 h = 0;
|
||||
u16 s = 0;
|
||||
u16 v = scale8_to_16_accurate(mx);
|
||||
|
||||
// Calculate saturation using improved scaling
|
||||
if (mx > 0) {
|
||||
// s = (delta * 65535) / mx, but with better accuracy
|
||||
// Use the same technique as scale8_to_16_accurate but for arbitrary denominator
|
||||
if (delta == mx) {
|
||||
s = 65535; // Saturation is 100%
|
||||
} else {
|
||||
s = (u16)(((u32)delta * 65535 + (mx >> 1)) / mx);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate hue using improved algorithms
|
||||
if (delta > 0) {
|
||||
u32 hue_calc = 0;
|
||||
|
||||
if (mx == r) {
|
||||
// Hue in red sector (0-60 degrees)
|
||||
if (g >= b) {
|
||||
// Use improved division: hue_calc = (g - b) * 65535 / (6 * delta)
|
||||
u32 numerator = (u32)(g - b) * 65535;
|
||||
if (delta <= 42) { // 6 * 42 = 252, safe for small delta
|
||||
hue_calc = numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = numerator / delta / 6; // Avoid overflow
|
||||
}
|
||||
} else {
|
||||
u32 numerator = (u32)(b - g) * 65535;
|
||||
if (delta <= 42) {
|
||||
hue_calc = 65535 - numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = 65535 - numerator / delta / 6;
|
||||
}
|
||||
}
|
||||
} else if (mx == g) {
|
||||
// Hue in green sector (60-180 degrees)
|
||||
// Handle signed arithmetic properly to avoid integer underflow
|
||||
i32 signed_diff = (i32)b - (i32)r;
|
||||
u32 sector_offset = 65535 / 3; // 60 degrees (120 degrees in 16-bit space)
|
||||
|
||||
if (signed_diff >= 0) {
|
||||
// Positive case: b >= r
|
||||
u32 numerator = (u32)signed_diff * 65535;
|
||||
if (delta <= 42) {
|
||||
hue_calc = sector_offset + numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = sector_offset + numerator / delta / 6;
|
||||
}
|
||||
} else {
|
||||
// Negative case: b < r
|
||||
u32 numerator = (u32)(-signed_diff) * 65535;
|
||||
if (delta <= 42) {
|
||||
hue_calc = sector_offset - numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = sector_offset - numerator / delta / 6;
|
||||
}
|
||||
}
|
||||
} else { // mx == b
|
||||
// Hue in blue sector (180-300 degrees)
|
||||
// Handle signed arithmetic properly to avoid integer underflow
|
||||
i32 signed_diff = (i32)r - (i32)g;
|
||||
u32 sector_offset = (2 * 65535) / 3; // 240 degrees (240 degrees in 16-bit space)
|
||||
|
||||
if (signed_diff >= 0) {
|
||||
// Positive case: r >= g
|
||||
u32 numerator = (u32)signed_diff * 65535;
|
||||
if (delta <= 42) {
|
||||
hue_calc = sector_offset + numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = sector_offset + numerator / delta / 6;
|
||||
}
|
||||
} else {
|
||||
// Negative case: r < g
|
||||
u32 numerator = (u32)(-signed_diff) * 65535;
|
||||
if (delta <= 42) {
|
||||
hue_calc = sector_offset - numerator / (6 * delta);
|
||||
} else {
|
||||
hue_calc = sector_offset - numerator / delta / 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h = (u16)(hue_calc & 0xFFFF);
|
||||
}
|
||||
|
||||
return HSV16{h, s, v};
|
||||
}
|
||||
|
||||
static CRGB HSV16toRGB(const HSV16& hsv) {
|
||||
// Convert 16-bit values to working range
|
||||
u32 h = hsv.h;
|
||||
u32 s = hsv.s;
|
||||
u32 v = hsv.v;
|
||||
|
||||
if (s == 0) {
|
||||
// Grayscale case - use precise mapping
|
||||
u8 gray = map16_to_8(v);
|
||||
return CRGB{gray, gray, gray};
|
||||
}
|
||||
|
||||
// Determine which sector of the color wheel (0-5)
|
||||
u32 sector = (h * 6) / 65536;
|
||||
u32 sector_pos = (h * 6) % 65536; // Position within sector (0-65535)
|
||||
|
||||
// Calculate intermediate values using precise mapping
|
||||
// c = v * s / 65536, with proper rounding
|
||||
u32 c = map32_to_16(v * s);
|
||||
|
||||
// Calculate x = c * (1 - |2*(sector_pos/65536) - 1|)
|
||||
u32 x;
|
||||
if (sector & 1) {
|
||||
// For odd sectors (1, 3, 5), we want decreasing values
|
||||
// x = c * (65535 - sector_pos) / 65535
|
||||
x = map32_to_16(c * (65535 - sector_pos));
|
||||
} else {
|
||||
// For even sectors (0, 2, 4), we want increasing values
|
||||
// x = c * sector_pos / 65535
|
||||
x = map32_to_16(c * sector_pos);
|
||||
}
|
||||
|
||||
u32 m = v - c;
|
||||
|
||||
u32 r1, g1, b1;
|
||||
switch (sector) {
|
||||
case 0: r1 = c; g1 = x; b1 = 0; break;
|
||||
case 1: r1 = x; g1 = c; b1 = 0; break;
|
||||
case 2: r1 = 0; g1 = c; b1 = x; break;
|
||||
case 3: r1 = 0; g1 = x; b1 = c; break;
|
||||
case 4: r1 = x; g1 = 0; b1 = c; break;
|
||||
default: r1 = c; g1 = 0; b1 = x; break;
|
||||
}
|
||||
|
||||
// Add baseline and scale to 8-bit using accurate mapping
|
||||
u8 R = map16_to_8(u16(r1 + m));
|
||||
u8 G = map16_to_8(u16(g1 + m));
|
||||
u8 B = map16_to_8(u16(b1 + m));
|
||||
|
||||
return CRGB{R, G, B};
|
||||
}
|
||||
|
||||
HSV16::HSV16(const CRGB& rgb) {
|
||||
*this = RGBtoHSV16(rgb);
|
||||
}
|
||||
|
||||
CRGB HSV16::ToRGB() const {
|
||||
return HSV16toRGB(*this);
|
||||
}
|
||||
|
||||
CRGB HSV16::colorBoost(EaseType saturation_function, EaseType luminance_function) const {
|
||||
HSV16 hsv = *this;
|
||||
|
||||
if (saturation_function != EASE_NONE) {
|
||||
u16 inv_sat = 65535 - hsv.s;
|
||||
inv_sat = ease16(saturation_function, inv_sat);
|
||||
hsv.s = (65535 - inv_sat);
|
||||
}
|
||||
|
||||
if (luminance_function != EASE_NONE) {
|
||||
hsv.v = ease16(luminance_function, hsv.v);
|
||||
}
|
||||
|
||||
return hsv.ToRGB();
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
38
libraries/FastLED/src/fl/hsv16.h
Normal file
38
libraries/FastLED/src/fl/hsv16.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/ease.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
struct HSV16 {
|
||||
u16 h = 0;
|
||||
u16 s = 0;
|
||||
u16 v = 0;
|
||||
|
||||
HSV16() = default;
|
||||
HSV16(u16 h, u16 s, u16 v) : h(h), s(s), v(v) {}
|
||||
HSV16(const CRGB& rgb);
|
||||
|
||||
// Rule of 5 for POD data
|
||||
HSV16(const HSV16 &other) = default;
|
||||
HSV16 &operator=(const HSV16 &other) = default;
|
||||
HSV16(HSV16 &&other) noexcept = default;
|
||||
HSV16 &operator=(HSV16 &&other) noexcept = default;
|
||||
|
||||
CRGB ToRGB() const;
|
||||
|
||||
/// Automatic conversion operator to CRGB
|
||||
/// Allows HSV16 to be automatically converted to CRGB
|
||||
operator CRGB() const { return ToRGB(); }
|
||||
|
||||
// Are you using WS2812 (or other RGB8 LEDS) to display video?
|
||||
// decimate the color? Use colorBoost() to boost the saturation.
|
||||
// This works great for WS2812 and any other RGB8 LEDs.
|
||||
// Default saturation function is similar to gamma correction.
|
||||
CRGB colorBoost(EaseType saturation_function = EASE_IN_QUAD, EaseType luminance_function = EASE_NONE) const;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
81
libraries/FastLED/src/fl/id_tracker.cpp
Normal file
81
libraries/FastLED/src/fl/id_tracker.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "fl/id_tracker.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
int IdTracker::getOrCreateId(void* ptr) {
|
||||
if (!ptr) {
|
||||
return -1; // Invalid pointer gets invalid ID
|
||||
}
|
||||
|
||||
// Lock for thread safety
|
||||
mMutex.lock();
|
||||
|
||||
// Check if ID already exists
|
||||
const int* existingId = mPointerToId.find_value(ptr);
|
||||
if (existingId) {
|
||||
int id = *existingId;
|
||||
mMutex.unlock();
|
||||
return id;
|
||||
}
|
||||
|
||||
// Create new ID
|
||||
int newId = mNextId++;
|
||||
mPointerToId[ptr] = newId;
|
||||
|
||||
mMutex.unlock();
|
||||
return newId;
|
||||
}
|
||||
|
||||
bool IdTracker::getId(void* ptr, int* outId) {
|
||||
if (!ptr || !outId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock for thread safety
|
||||
mMutex.lock();
|
||||
|
||||
const int* existingId = mPointerToId.find_value(ptr);
|
||||
bool found = (existingId != nullptr);
|
||||
if (found) {
|
||||
*outId = *existingId;
|
||||
}
|
||||
|
||||
mMutex.unlock();
|
||||
return found;
|
||||
}
|
||||
|
||||
bool IdTracker::removeId(void* ptr) {
|
||||
if (!ptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock for thread safety
|
||||
mMutex.lock();
|
||||
|
||||
bool removed = mPointerToId.erase(ptr);
|
||||
|
||||
mMutex.unlock();
|
||||
return removed;
|
||||
}
|
||||
|
||||
size_t IdTracker::size() {
|
||||
// Lock for thread safety
|
||||
mMutex.lock();
|
||||
|
||||
size_t currentSize = mPointerToId.size();
|
||||
|
||||
mMutex.unlock();
|
||||
return currentSize;
|
||||
}
|
||||
|
||||
void IdTracker::clear() {
|
||||
// Lock for thread safety
|
||||
mMutex.lock();
|
||||
|
||||
mPointerToId.clear();
|
||||
mNextId = 0; // Reset ID counter to start at 0
|
||||
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
96
libraries/FastLED/src/fl/id_tracker.h
Normal file
96
libraries/FastLED/src/fl/id_tracker.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/hash_map.h"
|
||||
#include "fl/mutex.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/**
|
||||
* Thread-safe ID tracker that maps void* pointers to unique integer IDs.
|
||||
*
|
||||
* Features:
|
||||
* - Auto-incrementing ID counter for new entries
|
||||
* - Thread-safe operations with mutex protection
|
||||
* - Instantiable class - create as many trackers as needed
|
||||
* - Support for removal of tracked pointers
|
||||
*
|
||||
* Usage:
|
||||
* IdTracker tracker; // Create instance
|
||||
* int id = tracker.getOrCreateId(ptr);
|
||||
* bool found = tracker.getId(ptr, &id);
|
||||
* tracker.removeId(ptr);
|
||||
*
|
||||
* For singleton behavior, wrap in your own singleton:
|
||||
* static IdTracker& getGlobalTracker() {
|
||||
* static IdTracker instance;
|
||||
* return instance;
|
||||
* }
|
||||
*/
|
||||
class IdTracker {
|
||||
public:
|
||||
/**
|
||||
* Default constructor - creates a new ID tracker instance
|
||||
*/
|
||||
IdTracker() = default;
|
||||
|
||||
/**
|
||||
* Get existing ID for pointer, or create a new one if not found.
|
||||
* Thread-safe.
|
||||
*
|
||||
* @param ptr Pointer to track
|
||||
* @return Unique integer ID for this pointer
|
||||
*/
|
||||
int getOrCreateId(void* ptr);
|
||||
|
||||
/**
|
||||
* Get existing ID for pointer without creating a new one.
|
||||
* Thread-safe.
|
||||
*
|
||||
* @param ptr Pointer to look up
|
||||
* @param outId Pointer to store the ID if found
|
||||
* @return true if ID was found, false if pointer not tracked
|
||||
*/
|
||||
bool getId(void* ptr, int* outId);
|
||||
|
||||
/**
|
||||
* Remove tracking for a pointer.
|
||||
* Thread-safe.
|
||||
*
|
||||
* @param ptr Pointer to stop tracking
|
||||
* @return true if pointer was being tracked and removed, false if not found
|
||||
*/
|
||||
bool removeId(void* ptr);
|
||||
|
||||
/**
|
||||
* Get the current number of tracked pointers.
|
||||
* Thread-safe.
|
||||
*
|
||||
* @return Number of currently tracked pointers
|
||||
*/
|
||||
size_t size();
|
||||
|
||||
/**
|
||||
* Clear all tracked pointers and reset ID counter.
|
||||
* Thread-safe.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
// Non-copyable and non-movable for thread safety
|
||||
// (Each instance should have its own independent state)
|
||||
IdTracker(const IdTracker&) = delete;
|
||||
IdTracker& operator=(const IdTracker&) = delete;
|
||||
IdTracker(IdTracker&&) = delete;
|
||||
IdTracker& operator=(IdTracker&&) = delete;
|
||||
|
||||
private:
|
||||
|
||||
// Thread synchronization
|
||||
mutable fl::mutex mMutex;
|
||||
|
||||
// ID mapping and counter
|
||||
fl::hash_map<void*, int> mPointerToId;
|
||||
int mNextId = 0; // Start IDs at 0 to match StripIdMap behavior
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
62
libraries/FastLED/src/fl/initializer_list.h
Normal file
62
libraries/FastLED/src/fl/initializer_list.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// allow-include-after-namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
// Define if initializer_list is available
|
||||
// Check for C++11 and if std::initializer_list exists
|
||||
#if defined(__AVR__)
|
||||
// Emulated initializer_list for AVR platforms
|
||||
namespace fl {
|
||||
template<typename T>
|
||||
class initializer_list {
|
||||
private:
|
||||
const T* mBegin;
|
||||
fl::size mSize;
|
||||
|
||||
// Private constructor used by compiler
|
||||
constexpr initializer_list(const T* first, fl::size size)
|
||||
: mBegin(first), mSize(size) {}
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = const T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = fl::size;
|
||||
using iterator = const T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
// Default constructor
|
||||
constexpr initializer_list() : mBegin(nullptr), mSize(0) {}
|
||||
|
||||
// Size and capacity
|
||||
constexpr fl::size size() const { return mSize; }
|
||||
constexpr bool empty() const { return mSize == 0; }
|
||||
|
||||
// Iterators
|
||||
constexpr const_iterator begin() const { return mBegin; }
|
||||
constexpr const_iterator end() const { return mBegin + mSize; }
|
||||
|
||||
// Allow compiler access to private constructor
|
||||
template<typename U> friend class initializer_list;
|
||||
};
|
||||
|
||||
// Helper functions to match std::initializer_list interface
|
||||
template<typename T>
|
||||
constexpr const T* begin(initializer_list<T> il) {
|
||||
return il.begin();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr const T* end(initializer_list<T> il) {
|
||||
return il.end();
|
||||
}
|
||||
}
|
||||
#else
|
||||
#include <initializer_list>
|
||||
namespace fl {
|
||||
using std::initializer_list;
|
||||
}
|
||||
#endif
|
||||
22
libraries/FastLED/src/fl/inplacenew.h
Normal file
22
libraries/FastLED/src/fl/inplacenew.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "fl/stdint.h"
|
||||
#include "fl/int.h"
|
||||
// This file must not be in the fl namespace, it must be in the global
|
||||
// namespace.
|
||||
|
||||
#if (defined(__AVR__) || !defined(__has_include)) && (!defined(FASTLED_HAS_NEW))
|
||||
#ifndef __has_include
|
||||
#define _NO_EXCEPT
|
||||
#else
|
||||
#define _NO_EXCEPT noexcept
|
||||
#endif
|
||||
inline void *operator new(fl::size, void *ptr) _NO_EXCEPT { return ptr; }
|
||||
#elif __has_include(<new>)
|
||||
#include <new>
|
||||
#elif __has_include(<new.h>)
|
||||
#include <new.h>
|
||||
#elif __has_include("new.h")
|
||||
#include "new.h"
|
||||
#endif
|
||||
16
libraries/FastLED/src/fl/insert_result.h
Normal file
16
libraries/FastLED/src/fl/insert_result.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Because of the fixed size nature of a lot of FastLED's containers we
|
||||
// need to provide additional feedback to the caller about the nature of
|
||||
// why an insert did or did not happen. Specifically, we want to differentiate
|
||||
// between failing to insert because the item already existed and when the
|
||||
// container was full.
|
||||
enum InsertResult {
|
||||
kInserted = 0,
|
||||
kExists = 1,
|
||||
kMaxSize = 2,
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
94
libraries/FastLED/src/fl/int.h
Normal file
94
libraries/FastLED/src/fl/int.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/// IMPORTANT!
|
||||
/// This file MUST not
|
||||
|
||||
#include "fl/stdint.h" // For uintptr_t and size_t
|
||||
|
||||
// Platform-specific integer type definitions
|
||||
// This includes platform-specific 16/32/64-bit types
|
||||
#include "platforms/int.h"
|
||||
|
||||
namespace fl {
|
||||
// 8-bit types - char is reliably 8 bits on all supported platforms
|
||||
// These must be defined BEFORE platform includes so fractional types can use them
|
||||
typedef signed char i8;
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned int uint;
|
||||
|
||||
// Pointer and size types are defined per-platform in platforms/int.h
|
||||
// uptr (pointer type) and size (size type) are defined per-platform
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace fl {
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Fixed-Point Fractional Types.
|
||||
/// Types for storing fractional data.
|
||||
///
|
||||
/// * ::sfract7 should be interpreted as signed 128ths.
|
||||
/// * ::fract8 should be interpreted as unsigned 256ths.
|
||||
/// * ::sfract15 should be interpreted as signed 32768ths.
|
||||
/// * ::fract16 should be interpreted as unsigned 65536ths.
|
||||
///
|
||||
/// Example: if a fract8 has the value "64", that should be interpreted
|
||||
/// as 64/256ths, or one-quarter.
|
||||
///
|
||||
/// accumXY types should be interpreted as X bits of integer,
|
||||
/// and Y bits of fraction.
|
||||
/// E.g., ::accum88 has 8 bits of int, 8 bits of fraction
|
||||
///
|
||||
|
||||
/// ANSI: unsigned short _Fract.
|
||||
/// Range is 0 to 0.99609375 in steps of 0.00390625.
|
||||
/// Should be interpreted as unsigned 256ths.
|
||||
typedef u8 fract8;
|
||||
|
||||
/// ANSI: signed short _Fract.
|
||||
/// Range is -0.9921875 to 0.9921875 in steps of 0.0078125.
|
||||
/// Should be interpreted as signed 128ths.
|
||||
typedef i8 sfract7;
|
||||
|
||||
/// ANSI: unsigned _Fract.
|
||||
/// Range is 0 to 0.99998474121 in steps of 0.00001525878.
|
||||
/// Should be interpreted as unsigned 65536ths.
|
||||
typedef u16 fract16;
|
||||
|
||||
typedef i32 sfract31; ///< ANSI: signed long _Fract. 31 bits int, 1 bit fraction
|
||||
|
||||
typedef u32 fract32; ///< ANSI: unsigned long _Fract. 32 bits int, 32 bits fraction
|
||||
|
||||
/// ANSI: signed _Fract.
|
||||
/// Range is -0.99996948242 to 0.99996948242 in steps of 0.00003051757.
|
||||
/// Should be interpreted as signed 32768ths.
|
||||
typedef i16 sfract15;
|
||||
|
||||
typedef u16 accum88; ///< ANSI: unsigned short _Accum. 8 bits int, 8 bits fraction
|
||||
typedef i16 saccum78; ///< ANSI: signed short _Accum. 7 bits int, 8 bits fraction
|
||||
typedef u32 accum1616; ///< ANSI: signed _Accum. 16 bits int, 16 bits fraction
|
||||
typedef i32 saccum1516; ///< ANSI: signed _Accum. 15 bits int, 16 bits fraction
|
||||
typedef u16 accum124; ///< no direct ANSI counterpart. 12 bits int, 4 bits fraction
|
||||
typedef i32 saccum114; ///< no direct ANSI counterpart. 1 bit int, 14 bits fraction
|
||||
}
|
||||
|
||||
namespace fl {
|
||||
// Size assertions moved to src/platforms/compile_test.cpp.hpp
|
||||
}
|
||||
|
||||
// Make fractional types available in global namespace
|
||||
using fl::fract8;
|
||||
using fl::sfract7;
|
||||
using fl::fract16;
|
||||
using fl::sfract31;
|
||||
using fl::fract32;
|
||||
using fl::sfract15;
|
||||
using fl::accum88;
|
||||
using fl::saccum78;
|
||||
using fl::accum1616;
|
||||
using fl::saccum1516;
|
||||
using fl::accum124;
|
||||
using fl::saccum114;
|
||||
225
libraries/FastLED/src/fl/io.cpp
Normal file
225
libraries/FastLED/src/fl/io.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "io.h"
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
// Platform-specific I/O implementations
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "platforms/wasm/io_wasm.h"
|
||||
#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||
#include "platforms/io_native.h"
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
#include "platforms/esp/io_esp.h"
|
||||
#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include "platforms/avr/io_avr.h"
|
||||
#elif defined(__MKL26Z64__)
|
||||
// Teensy LC has special handling to avoid _write linker issues
|
||||
#include "platforms/io_teensy_lc.h"
|
||||
#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__)
|
||||
// All other Teensy platforms use lightweight implementation to avoid Serial library bloat
|
||||
#include "platforms/io_teensy.h"
|
||||
#else
|
||||
#include "platforms/io_arduino.h"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
// Static storage for injected handlers using lazy initialization to avoid global constructors
|
||||
static print_handler_t& get_print_handler() {
|
||||
static print_handler_t handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
static println_handler_t& get_println_handler() {
|
||||
static println_handler_t handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
static available_handler_t& get_available_handler() {
|
||||
static available_handler_t handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
static read_handler_t& get_read_handler() {
|
||||
static read_handler_t handler;
|
||||
return handler;
|
||||
}
|
||||
#endif
|
||||
|
||||
void print(const char* str) {
|
||||
if (!str) return;
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
// Check for injected handler first
|
||||
if (get_print_handler()) {
|
||||
get_print_handler()(str);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
print_wasm(str);
|
||||
#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||
print_native(str);
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
print_esp(str);
|
||||
#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)
|
||||
print_avr(str);
|
||||
#elif defined(__MKL26Z64__)
|
||||
// Teensy LC uses special no-op functions to avoid _write linker issues
|
||||
print_teensy_lc(str);
|
||||
#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__)
|
||||
// All other Teensy platforms use lightweight implementation
|
||||
print_teensy(str);
|
||||
#else
|
||||
// Use generic Arduino print for all other platforms including:
|
||||
// - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA)
|
||||
// - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK)
|
||||
// - All other Arduino-compatible platforms
|
||||
print_arduino(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
void println(const char* str) {
|
||||
if (!str) return;
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
// Check for injected handler first
|
||||
if (get_println_handler()) {
|
||||
get_println_handler()(str);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
println_wasm(str);
|
||||
#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||
println_native(str);
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
println_esp(str);
|
||||
#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)
|
||||
println_avr(str);
|
||||
#elif defined(__MKL26Z64__)
|
||||
// Teensy LC uses special no-op functions to avoid _write linker issues
|
||||
println_teensy_lc(str);
|
||||
#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__)
|
||||
// All other Teensy platforms use lightweight implementation
|
||||
println_teensy(str);
|
||||
#else
|
||||
// Use generic Arduino print for all other platforms including:
|
||||
// - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA)
|
||||
// - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK)
|
||||
// - All other Arduino-compatible platforms
|
||||
println_arduino(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
int available() {
|
||||
#ifdef FASTLED_TESTING
|
||||
// Check for injected handler first
|
||||
if (get_available_handler()) {
|
||||
return get_available_handler()();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return available_wasm();
|
||||
#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||
return available_native();
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
return available_esp();
|
||||
#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)
|
||||
return available_avr();
|
||||
#elif defined(__MKL26Z64__)
|
||||
// Teensy LC uses special no-op functions to avoid _write linker issues
|
||||
return available_teensy_lc();
|
||||
#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__)
|
||||
// All other Teensy platforms use lightweight implementation
|
||||
return available_teensy();
|
||||
#else
|
||||
// Use generic Arduino input for all other platforms including:
|
||||
// - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA)
|
||||
// - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK)
|
||||
// - All other Arduino-compatible platforms
|
||||
return available_arduino();
|
||||
#endif
|
||||
}
|
||||
|
||||
int read() {
|
||||
#ifdef FASTLED_TESTING
|
||||
// Check for injected handler first
|
||||
if (get_read_handler()) {
|
||||
return get_read_handler()();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return read_wasm();
|
||||
#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||
return read_native();
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
return read_esp();
|
||||
#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)
|
||||
return read_avr();
|
||||
#elif defined(__MKL26Z64__)
|
||||
// Teensy LC uses special no-op functions to avoid _write linker issues
|
||||
return read_teensy_lc();
|
||||
#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__)
|
||||
// All other Teensy platforms use lightweight implementation
|
||||
return read_teensy();
|
||||
#else
|
||||
// Use generic Arduino input for all other platforms including:
|
||||
// - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA)
|
||||
// - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK)
|
||||
// - All other Arduino-compatible platforms
|
||||
return read_arduino();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
|
||||
// Inject function handlers for testing
|
||||
void inject_print_handler(const print_handler_t& handler) {
|
||||
get_print_handler() = handler;
|
||||
}
|
||||
|
||||
void inject_println_handler(const println_handler_t& handler) {
|
||||
get_println_handler() = handler;
|
||||
}
|
||||
|
||||
void inject_available_handler(const available_handler_t& handler) {
|
||||
get_available_handler() = handler;
|
||||
}
|
||||
|
||||
void inject_read_handler(const read_handler_t& handler) {
|
||||
get_read_handler() = handler;
|
||||
}
|
||||
|
||||
// Clear all injected handlers (restores default behavior)
|
||||
void clear_io_handlers() {
|
||||
get_print_handler() = print_handler_t{};
|
||||
get_println_handler() = println_handler_t{};
|
||||
get_available_handler() = available_handler_t{};
|
||||
get_read_handler() = read_handler_t{};
|
||||
}
|
||||
|
||||
// Clear individual handlers
|
||||
void clear_print_handler() {
|
||||
get_print_handler() = print_handler_t{};
|
||||
}
|
||||
|
||||
void clear_println_handler() {
|
||||
get_println_handler() = println_handler_t{};
|
||||
}
|
||||
|
||||
void clear_available_handler() {
|
||||
get_available_handler() = available_handler_t{};
|
||||
}
|
||||
|
||||
void clear_read_handler() {
|
||||
get_read_handler() = read_handler_t{};
|
||||
}
|
||||
|
||||
#endif // FASTLED_TESTING
|
||||
|
||||
} // namespace fl
|
||||
61
libraries/FastLED/src/fl/io.h
Normal file
61
libraries/FastLED/src/fl/io.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#define FL_IO_H_INCLUDED
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
#include "fl/function.h"
|
||||
#endif
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Low-level print functions that avoid printf/sprintf dependencies
|
||||
// These use the most efficient output method for each platform
|
||||
|
||||
// Print a string without newline
|
||||
void print(const char* str);
|
||||
|
||||
// Print a string with newline
|
||||
#ifndef FL_DBG_PRINTLN_DECLARED
|
||||
void println(const char* str);
|
||||
#else
|
||||
// Declaration already exists from fl/dbg.h
|
||||
#endif
|
||||
|
||||
// Low-level input functions that provide Serial-style read functionality
|
||||
// These use the most efficient input method for each platform
|
||||
|
||||
// Returns the number of bytes available to read from input stream
|
||||
// Returns 0 if no data is available
|
||||
int available();
|
||||
|
||||
// Reads the next byte from input stream
|
||||
// Returns the byte value (0-255) if data is available
|
||||
// Returns -1 if no data is available
|
||||
int read();
|
||||
|
||||
#ifdef FASTLED_TESTING
|
||||
|
||||
// Testing function handler types
|
||||
using print_handler_t = fl::function<void(const char*)>;
|
||||
using println_handler_t = fl::function<void(const char*)>;
|
||||
using available_handler_t = fl::function<int()>;
|
||||
using read_handler_t = fl::function<int()>;
|
||||
|
||||
// Inject function handlers for testing
|
||||
void inject_print_handler(const print_handler_t& handler);
|
||||
void inject_println_handler(const println_handler_t& handler);
|
||||
void inject_available_handler(const available_handler_t& handler);
|
||||
void inject_read_handler(const read_handler_t& handler);
|
||||
|
||||
// Clear all injected handlers (restores default behavior)
|
||||
void clear_io_handlers();
|
||||
|
||||
// Clear individual handlers
|
||||
void clear_print_handler();
|
||||
void clear_println_handler();
|
||||
void clear_available_handler();
|
||||
void clear_read_handler();
|
||||
|
||||
#endif // FASTLED_TESTING
|
||||
|
||||
} // namespace fl
|
||||
4
libraries/FastLED/src/fl/iostream.h
Normal file
4
libraries/FastLED/src/fl/iostream.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/ostream.h"
|
||||
#include "fl/istream.h"
|
||||
416
libraries/FastLED/src/fl/istream.cpp
Normal file
416
libraries/FastLED/src/fl/istream.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#include "istream.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/compiler_control.h"
|
||||
//#include <stddef.h>
|
||||
// <cstdlib> not available on AVR platforms like Arduino UNO
|
||||
// We implement custom integer parsing functions instead
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
// Helper function to check if a character is a digit
|
||||
inline bool isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
// Helper function to check if a character is whitespace
|
||||
inline bool isSpace(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
|
||||
}
|
||||
|
||||
// Custom integer parsing function for signed 32-bit integers
|
||||
bool parse_i32(const char* str, fl::i32& result) {
|
||||
if (!str) return false;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*str && isSpace(*str)) {
|
||||
str++;
|
||||
}
|
||||
|
||||
if (*str == '\0') return false;
|
||||
|
||||
bool negative = false;
|
||||
if (*str == '-') {
|
||||
negative = true;
|
||||
str++;
|
||||
} else if (*str == '+') {
|
||||
str++;
|
||||
}
|
||||
|
||||
if (*str == '\0' || !isDigit(*str)) return false;
|
||||
|
||||
fl::u32 value = 0;
|
||||
const fl::u32 max_div_10 = 214748364U; // INT32_MAX / 10
|
||||
const fl::u32 max_mod_10 = 7U; // INT32_MAX % 10
|
||||
const fl::u32 max_neg_div_10 = 214748364U; // -INT32_MIN / 10
|
||||
const fl::u32 max_neg_mod_10 = 8U; // -INT32_MIN % 10
|
||||
|
||||
while (*str && isDigit(*str)) {
|
||||
fl::u32 digit = *str - '0';
|
||||
|
||||
// Check for overflow
|
||||
if (negative) {
|
||||
if (value > max_neg_div_10 || (value == max_neg_div_10 && digit > max_neg_mod_10)) {
|
||||
return false; // Overflow
|
||||
}
|
||||
} else {
|
||||
if (value > max_div_10 || (value == max_div_10 && digit > max_mod_10)) {
|
||||
return false; // Overflow
|
||||
}
|
||||
}
|
||||
|
||||
value = value * 10 + digit;
|
||||
str++;
|
||||
}
|
||||
|
||||
// Check if we stopped at a non-digit character (should be end of string for valid parse)
|
||||
if (*str != '\0') return false;
|
||||
|
||||
if (negative) {
|
||||
result = -static_cast<fl::i32>(value);
|
||||
} else {
|
||||
result = static_cast<fl::i32>(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Custom integer parsing function for unsigned 32-bit integers
|
||||
bool parse_u32(const char* str, fl::u32& result) {
|
||||
if (!str) return false;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*str && isSpace(*str)) {
|
||||
str++;
|
||||
}
|
||||
|
||||
if (*str == '\0') return false;
|
||||
|
||||
// Optional '+' sign (no '-' for unsigned)
|
||||
if (*str == '+') {
|
||||
str++;
|
||||
} else if (*str == '-') {
|
||||
return false; // Negative not allowed for unsigned
|
||||
}
|
||||
|
||||
if (*str == '\0' || !isDigit(*str)) return false;
|
||||
|
||||
fl::u32 value = 0;
|
||||
const fl::u32 max_div_10 = 429496729U; // UINT32_MAX / 10
|
||||
const fl::u32 max_mod_10 = 5U; // UINT32_MAX % 10
|
||||
|
||||
while (*str && isDigit(*str)) {
|
||||
fl::u32 digit = *str - '0';
|
||||
|
||||
// Check for overflow
|
||||
if (value > max_div_10 || (value == max_div_10 && digit > max_mod_10)) {
|
||||
return false; // Overflow
|
||||
}
|
||||
|
||||
value = value * 10 + digit;
|
||||
str++;
|
||||
}
|
||||
|
||||
// Check if we stopped at a non-digit character (should be end of string for valid parse)
|
||||
if (*str != '\0') return false;
|
||||
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
// Global cin instance (stub that conditionally delegates)
|
||||
istream cin;
|
||||
|
||||
// Function to get singleton instance of istream_real (for better linker elimination)
|
||||
istream_real& cin_real() {
|
||||
// Local static instance - only constructed when first called
|
||||
// This allows the linker to eliminate it if never referenced
|
||||
static istream_real instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool istream_real::readLine() {
|
||||
// If we have no more data available and no buffered data, we're at EOF
|
||||
if (pos_ >= buffer_len_ && fl::available() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read characters until newline or no more data
|
||||
buffer_len_ = 0;
|
||||
while (fl::available() > 0 && buffer_len_ < BUFFER_SIZE - 1) {
|
||||
int c = fl::read();
|
||||
if (c == -1) break;
|
||||
if (c == '\n') break;
|
||||
if (c == '\r') continue; // Skip carriage return
|
||||
buffer_[buffer_len_++] = static_cast<char>(c);
|
||||
}
|
||||
|
||||
// Null terminate the buffer
|
||||
buffer_[buffer_len_] = '\0';
|
||||
pos_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void istream_real::skipWhitespace() {
|
||||
while (pos_ < buffer_len_ &&
|
||||
(buffer_[pos_] == ' ' || buffer_[pos_] == '\t' ||
|
||||
buffer_[pos_] == '\n' || buffer_[pos_] == '\r')) {
|
||||
pos_++;
|
||||
}
|
||||
|
||||
// If we've consumed all buffer and there's more input, read more
|
||||
if (pos_ >= buffer_len_ && fl::available() > 0) {
|
||||
if (readLine()) {
|
||||
skipWhitespace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool istream_real::readToken(string& token) {
|
||||
skipWhitespace();
|
||||
|
||||
if (pos_ >= buffer_len_ && fl::available() == 0) {
|
||||
failed_ = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If buffer is empty but data is available, read a line
|
||||
if (pos_ >= buffer_len_ && fl::available() > 0) {
|
||||
if (!readLine()) {
|
||||
failed_ = true;
|
||||
return false;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
// Read until whitespace or end of buffer
|
||||
token.clear();
|
||||
while (pos_ < buffer_len_ &&
|
||||
buffer_[pos_] != ' ' && buffer_[pos_] != '\t' &&
|
||||
buffer_[pos_] != '\n' && buffer_[pos_] != '\r') {
|
||||
// Explicitly append as a character string to avoid fl::u8->number conversion
|
||||
char ch[2] = {buffer_[pos_], '\0'};
|
||||
token.append(ch, 1);
|
||||
pos_++;
|
||||
}
|
||||
|
||||
return !token.empty();
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(string& str) {
|
||||
if (!readToken(str)) {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(char& c) {
|
||||
skipWhitespace();
|
||||
|
||||
if (pos_ >= buffer_len_ && fl::available() > 0) {
|
||||
if (!readLine()) {
|
||||
failed_ = true;
|
||||
return *this;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
if (pos_ < buffer_len_) {
|
||||
c = buffer_[pos_];
|
||||
pos_++;
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(fl::i8& n) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
fl::i32 temp;
|
||||
if (parse_i32(token.c_str(), temp) && temp >= -128 && temp <= 127) {
|
||||
n = static_cast<fl::i8>(temp);
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(fl::u8& n) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
fl::u32 temp;
|
||||
if (parse_u32(token.c_str(), temp) && temp <= 255) {
|
||||
n = static_cast<fl::u8>(temp);
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(fl::i16& n) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
fl::i32 temp;
|
||||
if (parse_i32(token.c_str(), temp) && temp >= -32768 && temp <= 32767) {
|
||||
n = static_cast<fl::i16>(temp);
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// u16 operator>> removed - now handled by template in header
|
||||
|
||||
istream_real& istream_real::operator>>(fl::i32& n) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
if (!parse_i32(token.c_str(), n)) {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(fl::u32& n) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
if (!parse_u32(token.c_str(), n)) {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(float& f) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
// Use the existing toFloat() method
|
||||
f = token.toFloat();
|
||||
// Check if parsing was successful by checking for valid float
|
||||
// toFloat() returns 0.0f for invalid input, but we need to distinguish
|
||||
// between actual 0.0f and parse failure
|
||||
if (ALMOST_EQUAL_FLOAT(f, 0.0f) && token != "0" && token != "0.0" && token != "0." && token.find("0") != 0) {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream_real& istream_real::operator>>(double& d) {
|
||||
string token;
|
||||
if (readToken(token)) {
|
||||
// Use the existing toFloat() method
|
||||
float f = token.toFloat();
|
||||
d = static_cast<double>(f);
|
||||
// Check if parsing was successful (same logic as float)
|
||||
if (ALMOST_EQUAL_FLOAT(f, 0.0f) && token != "0" && token != "0.0" && token != "0." && token.find("0") != 0) {
|
||||
failed_ = true;
|
||||
}
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// fl::size operator>> removed - now handled by template in header
|
||||
|
||||
istream_real& istream_real::getline(string& str) {
|
||||
str.clear();
|
||||
|
||||
// Read from current buffer position to end
|
||||
while (pos_ < buffer_len_) {
|
||||
if (buffer_[pos_] == '\n') {
|
||||
pos_++; // Consume the newline
|
||||
return *this;
|
||||
}
|
||||
// Explicitly append as a character string to avoid fl::u8->number conversion
|
||||
char ch[2] = {buffer_[pos_], '\0'};
|
||||
str.append(ch, 1);
|
||||
pos_++;
|
||||
}
|
||||
|
||||
// If we need more data, read a new line
|
||||
if (fl::available() > 0) {
|
||||
// Read more characters until newline
|
||||
while (fl::available() > 0) {
|
||||
int c = fl::read();
|
||||
if (c == -1) break;
|
||||
if (c == '\n') break;
|
||||
if (c == '\r') continue; // Skip carriage return
|
||||
// Explicitly append as a character string to avoid fl::u8->number conversion
|
||||
char ch[2] = {static_cast<char>(c), '\0'};
|
||||
str.append(ch, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
int istream_real::get() {
|
||||
if (pos_ >= buffer_len_ && fl::available() > 0) {
|
||||
if (!readLine()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos_ < buffer_len_) {
|
||||
return static_cast<int>(static_cast<unsigned char>(buffer_[pos_++]));
|
||||
}
|
||||
|
||||
// Try to read directly from input if buffer is empty
|
||||
return fl::read();
|
||||
}
|
||||
|
||||
istream_real& istream_real::putback(char c) {
|
||||
if (pos_ > 0) {
|
||||
pos_--;
|
||||
buffer_[pos_] = c;
|
||||
} else {
|
||||
// Insert at beginning of buffer - shift existing data
|
||||
if (buffer_len_ < BUFFER_SIZE - 1) {
|
||||
for (fl::size i = buffer_len_; i > 0; --i) {
|
||||
buffer_[i] = buffer_[i-1];
|
||||
}
|
||||
buffer_[0] = c;
|
||||
buffer_len_++;
|
||||
buffer_[buffer_len_] = '\0';
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
int istream_real::peek() {
|
||||
if (pos_ >= buffer_len_ && fl::available() > 0) {
|
||||
if (!readLine()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos_ < buffer_len_) {
|
||||
return static_cast<int>(static_cast<unsigned char>(buffer_[pos_]));
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
298
libraries/FastLED/src/fl/istream.h
Normal file
298
libraries/FastLED/src/fl/istream.h
Normal file
@@ -0,0 +1,298 @@
|
||||
// allow-include-after-namespace
|
||||
|
||||
#pragma once
|
||||
|
||||
// Forward declarations to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled
|
||||
#ifndef FL_IO_H_INCLUDED
|
||||
namespace fl {
|
||||
int available();
|
||||
int read();
|
||||
#ifdef FASTLED_TESTING
|
||||
template<typename T> class function; // Forward declare
|
||||
void clear_io_handlers();
|
||||
void inject_available_handler(const function<int()>& handler);
|
||||
void inject_read_handler(const function<int()>& handler);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "fl/str.h"
|
||||
#include "fl/type_traits.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
|
||||
#include "fl/int.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class istream_real {
|
||||
private:
|
||||
static const fl::size BUFFER_SIZE = 256;
|
||||
char buffer_[BUFFER_SIZE];
|
||||
fl::size buffer_len_ = 0;
|
||||
fl::size pos_ = 0;
|
||||
bool failed_ = false;
|
||||
|
||||
// Helper to read a line from input
|
||||
bool readLine();
|
||||
|
||||
// Helper to skip whitespace
|
||||
void skipWhitespace();
|
||||
|
||||
// Helper to read until whitespace or end
|
||||
bool readToken(string& token);
|
||||
|
||||
public:
|
||||
istream_real() = default;
|
||||
|
||||
// Check if stream is in good state
|
||||
bool good() const { return !failed_; }
|
||||
bool fail() const { return failed_; }
|
||||
bool eof() const { return pos_ >= buffer_len_ && fl::available() == 0; }
|
||||
|
||||
// Clear error state
|
||||
void clear() { failed_ = false; }
|
||||
|
||||
// Stream input operators
|
||||
istream_real& operator>>(string& str);
|
||||
istream_real& operator>>(char& c);
|
||||
istream_real& operator>>(fl::i8& n);
|
||||
istream_real& operator>>(fl::u8& n);
|
||||
istream_real& operator>>(fl::i16& n);
|
||||
istream_real& operator>>(fl::i32& n);
|
||||
istream_real& operator>>(fl::u32& n);
|
||||
istream_real& operator>>(float& f);
|
||||
istream_real& operator>>(double& d);
|
||||
|
||||
// Unified handler for fl:: namespace size-like unsigned integer types to avoid conflicts
|
||||
// This only handles fl::size and fl::u16 from the fl:: namespace
|
||||
template<typename T>
|
||||
typename fl::enable_if<
|
||||
fl::is_same<T, fl::size>::value ||
|
||||
fl::is_same<T, fl::u16>::value,
|
||||
istream_real&
|
||||
>::type operator>>(T& n);
|
||||
|
||||
// Get a line from input
|
||||
istream_real& getline(string& str);
|
||||
|
||||
// Get next character
|
||||
int get();
|
||||
|
||||
// Put back a character
|
||||
istream_real& putback(char c);
|
||||
|
||||
// Peek at next character without consuming it
|
||||
int peek();
|
||||
};
|
||||
|
||||
// Function to get singleton instance of istream_real (for better linker elimination)
|
||||
istream_real& cin_real();
|
||||
|
||||
// Stub istream class that conditionally delegates to istream_real
|
||||
class istream {
|
||||
private:
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
istream_real real_stream_;
|
||||
#endif
|
||||
|
||||
public:
|
||||
istream() = default;
|
||||
|
||||
// Check if stream is in good state
|
||||
bool good() const {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
return real_stream_.good();
|
||||
#else
|
||||
return true; // Always good on memory-constrained platforms
|
||||
#endif
|
||||
}
|
||||
|
||||
bool fail() const {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
return real_stream_.fail();
|
||||
#else
|
||||
return false; // Never fail on memory-constrained platforms
|
||||
#endif
|
||||
}
|
||||
|
||||
bool eof() const {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
return real_stream_.eof();
|
||||
#else
|
||||
return true; // Always EOF on memory-constrained platforms
|
||||
#endif
|
||||
}
|
||||
|
||||
// Clear error state
|
||||
void clear() {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Stream input operators
|
||||
istream& operator>>(string& str) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> str;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
str.clear();
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(char& c) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> c;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
c = '\0';
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(fl::i8& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(fl::u8& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(fl::i16& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(fl::i32& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(fl::u32& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(float& f) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> f;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
f = 0.0f;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
istream& operator>>(double& d) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> d;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
d = 0.0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Unified handler for fl:: namespace size-like unsigned integer types to avoid conflicts
|
||||
template<typename T>
|
||||
typename fl::enable_if<
|
||||
fl::is_same<T, fl::size>::value ||
|
||||
fl::is_same<T, fl::u16>::value,
|
||||
istream&
|
||||
>::type operator>>(T& n) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_ >> n;
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
n = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Get a line from input
|
||||
istream& getline(string& str) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_.getline(str);
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
str.clear();
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Get next character
|
||||
int get() {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
return real_stream_.get();
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Put back a character
|
||||
istream& putback(char c) {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
real_stream_.putback(c);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Peek at next character without consuming it
|
||||
int peek() {
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY
|
||||
return real_stream_.peek();
|
||||
#else
|
||||
// No-op on memory-constrained platforms
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// Global cin instance for input (now uses the stub)
|
||||
extern istream cin;
|
||||
|
||||
// Template implementation for istream_real
|
||||
template<typename T>
|
||||
typename fl::enable_if<
|
||||
fl::is_same<T, fl::size>::value ||
|
||||
fl::is_same<T, fl::u16>::value,
|
||||
istream_real&
|
||||
>::type istream_real::operator>>(T& n) {
|
||||
// Use existing fl::u32 parsing logic for both fl::size and fl::u16
|
||||
// since they're both unsigned integer types that fit in fl::u32
|
||||
fl::u32 temp;
|
||||
(*this) >> temp;
|
||||
n = static_cast<T>(temp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
509
libraries/FastLED/src/fl/json.cpp
Normal file
509
libraries/FastLED/src/fl/json.cpp
Normal file
@@ -0,0 +1,509 @@
|
||||
|
||||
#include "fl/json.h"
|
||||
#include "fl/string.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/deque.h"
|
||||
#include "fl/function.h"
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/math.h" // For floor function
|
||||
#include "fl/compiler_control.h"
|
||||
#include "fl/thread_local.h"
|
||||
|
||||
// Define INT16_MIN, INT16_MAX, and UINT8_MAX if not already defined
|
||||
#ifndef INT16_MIN
|
||||
#define INT16_MIN (-32768)
|
||||
#endif
|
||||
|
||||
#ifndef INT16_MAX
|
||||
#define INT16_MAX 32767
|
||||
#endif
|
||||
|
||||
#ifndef UINT8_MAX
|
||||
#define UINT8_MAX 255
|
||||
#endif
|
||||
|
||||
|
||||
#if FASTLED_ENABLE_JSON
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING_NULL_DEREFERENCE
|
||||
#include "third_party/arduinojson/json.h"
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
#endif // FASTLED_ENABLE_JSON
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
||||
|
||||
// Helper function to check if a double can be reasonably represented as a float
|
||||
// Used for debug logging - may appear unused in release builds
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(unused-function)
|
||||
static bool canBeRepresentedAsFloat(double value) {
|
||||
|
||||
|
||||
auto isnan = [](double value) -> bool {
|
||||
return value != value;
|
||||
};
|
||||
|
||||
// Check for special values
|
||||
if (isnan(value)) {
|
||||
return true; // These can be represented as float
|
||||
}
|
||||
|
||||
// Check if the value is within reasonable float range
|
||||
// Reject values that are clearly beyond float precision (beyond 2^24 for integers)
|
||||
// or outside the float range
|
||||
if (fl::fl_abs(value) > 16777216.0) { // 2^24 - beyond which floats lose integer precision
|
||||
return false;
|
||||
}
|
||||
|
||||
// For values within reasonable range, allow conversion even with minor precision loss
|
||||
// This handles cases like 300000.14159 which should be convertible to float
|
||||
// even though it loses some precision
|
||||
return true;
|
||||
}
|
||||
FL_DISABLE_WARNING_POP
|
||||
|
||||
|
||||
JsonValue& get_null_value() {
|
||||
static ThreadLocal<JsonValue> null_value;
|
||||
return null_value.access();
|
||||
}
|
||||
|
||||
JsonObject& get_empty_json_object() {
|
||||
// thread_local JsonObject empty_object;
|
||||
static ThreadLocal<JsonObject> empty_object;
|
||||
return empty_object.access();
|
||||
}
|
||||
|
||||
fl::shared_ptr<JsonValue> JsonValue::parse(const fl::string& txt) {
|
||||
#if !FASTLED_ENABLE_JSON
|
||||
return fl::make_shared<JsonValue>(fl::string(txt));
|
||||
#else
|
||||
// Determine the size of the JsonDocument needed.
|
||||
FLArduinoJson::JsonDocument doc;
|
||||
|
||||
FLArduinoJson::DeserializationError error = FLArduinoJson::deserializeJson(doc, txt.c_str());
|
||||
|
||||
if (error) {
|
||||
FL_WARN("JSON parsing failed: " << error.c_str());
|
||||
return fl::make_shared<JsonValue>(nullptr); // Return null on error
|
||||
}
|
||||
|
||||
// Helper function to convert FLArduinoJson::JsonVariantConst to fl::Json::JsonValue
|
||||
struct Converter {
|
||||
static fl::shared_ptr<JsonValue> convert(const FLArduinoJson::JsonVariantConst& src) {
|
||||
if (src.isNull()) {
|
||||
return fl::make_shared<JsonValue>(nullptr);
|
||||
} else if (src.is<bool>()) {
|
||||
return fl::make_shared<JsonValue>(src.as<bool>());
|
||||
} else if (src.is<int64_t>()) {
|
||||
// Handle 64-bit integers
|
||||
return fl::make_shared<JsonValue>(src.as<int64_t>());
|
||||
} else if (src.is<int32_t>()) {
|
||||
// Handle 32-bit integers explicitly for platform compatibility
|
||||
return fl::make_shared<JsonValue>(static_cast<int64_t>(src.as<int32_t>()));
|
||||
} else if (src.is<uint32_t>()) {
|
||||
// Handle unsigned 32-bit integers
|
||||
return fl::make_shared<JsonValue>(static_cast<int64_t>(src.as<uint32_t>()));
|
||||
} else if (src.is<double>()) {
|
||||
// Handle double precision floats - convert to float
|
||||
return fl::make_shared<JsonValue>(static_cast<float>(src.as<double>()));
|
||||
} else if (src.is<float>()) {
|
||||
// Handle single precision floats explicitly
|
||||
return fl::make_shared<JsonValue>(src.as<float>());
|
||||
} else if (src.is<const char*>()) {
|
||||
return fl::make_shared<JsonValue>(fl::string(src.as<const char*>()));
|
||||
} else if (src.is<FLArduinoJson::JsonArrayConst>()) {
|
||||
FLArduinoJson::JsonArrayConst arr = src.as<FLArduinoJson::JsonArrayConst>();
|
||||
|
||||
// Empty arrays should remain regular arrays
|
||||
if (arr.size() == 0) {
|
||||
return fl::make_shared<JsonValue>(JsonArray{});
|
||||
}
|
||||
|
||||
// Enum to represent array optimization types
|
||||
enum ArrayType {
|
||||
ALL_UINT8,
|
||||
ALL_INT16,
|
||||
ALL_FLOATS,
|
||||
GENERIC_ARRAY
|
||||
};
|
||||
|
||||
// Helper struct to track array type info
|
||||
struct ArrayTypeInfo {
|
||||
bool isUint8 = true;
|
||||
bool isInt16 = true;
|
||||
bool isFloat = true;
|
||||
|
||||
void disableAll() {
|
||||
isUint8 = false;
|
||||
isInt16 = false;
|
||||
isFloat = false;
|
||||
}
|
||||
|
||||
void checkNumericValue(double val) {
|
||||
// Check integer ranges in one pass
|
||||
bool isInteger = val == floor(val);
|
||||
if (!isInteger || val < 0 || val > UINT8_MAX) {
|
||||
isUint8 = false;
|
||||
}
|
||||
if (!isInteger || val < INT16_MIN || val > INT16_MAX) {
|
||||
isInt16 = false;
|
||||
}
|
||||
if (!canBeRepresentedAsFloat(val)) {
|
||||
isFloat = false;
|
||||
}
|
||||
}
|
||||
|
||||
void checkIntegerValue(int64_t val) {
|
||||
// Check all ranges in one pass
|
||||
if (val < 0 || val > UINT8_MAX) {
|
||||
isUint8 = false;
|
||||
}
|
||||
if (val < INT16_MIN || val > INT16_MAX) {
|
||||
isInt16 = false;
|
||||
}
|
||||
if (val < -16777216 || val > 16777216) {
|
||||
isFloat = false;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayType getBestType() const {
|
||||
if (isUint8) return ALL_UINT8;
|
||||
if (isInt16) return ALL_INT16;
|
||||
if (isFloat) return ALL_FLOATS;
|
||||
return GENERIC_ARRAY;
|
||||
}
|
||||
};
|
||||
|
||||
ArrayTypeInfo typeInfo;
|
||||
|
||||
#if FASTLED_DEBUG_LEVEL >= 2
|
||||
FASTLED_WARN("Array conversion: processing " << arr.size() << " items");
|
||||
#endif
|
||||
|
||||
for (const auto& item : arr) {
|
||||
// Check if all items are numeric
|
||||
if (!item.is<int32_t>() && !item.is<int64_t>() && !item.is<double>()) {
|
||||
typeInfo.disableAll();
|
||||
#if FASTLED_DEBUG_LEVEL >= 2
|
||||
FASTLED_WARN("Non-numeric value found, no optimization possible");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Update type flags based on item type
|
||||
if (item.is<double>()) {
|
||||
double val = item.as<double>();
|
||||
typeInfo.checkNumericValue(val);
|
||||
} else {
|
||||
int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
|
||||
typeInfo.checkIntegerValue(val);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the optimal array type based on the flags
|
||||
ArrayType arrayType = arr.size() > 0 ? typeInfo.getBestType() : GENERIC_ARRAY;
|
||||
|
||||
// Apply the most appropriate optimization based on determined type
|
||||
switch (arrayType) {
|
||||
case ALL_UINT8: {
|
||||
// All values fit in uint8_t - most compact representation
|
||||
fl::vector<uint8_t> byteData;
|
||||
for (const auto& item : arr) {
|
||||
if (item.is<double>()) {
|
||||
byteData.push_back(static_cast<uint8_t>(item.as<double>()));
|
||||
} else {
|
||||
int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
|
||||
byteData.push_back(static_cast<uint8_t>(val));
|
||||
}
|
||||
}
|
||||
return fl::make_shared<JsonValue>(fl::move(byteData));
|
||||
}
|
||||
case ALL_INT16: {
|
||||
// All values fit in int16_t - good compression
|
||||
fl::vector<int16_t> intData;
|
||||
for (const auto& item : arr) {
|
||||
if (item.is<double>()) {
|
||||
intData.push_back(static_cast<int16_t>(item.as<double>()));
|
||||
} else {
|
||||
int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
|
||||
intData.push_back(static_cast<int16_t>(val));
|
||||
}
|
||||
}
|
||||
return fl::make_shared<JsonValue>(fl::move(intData));
|
||||
}
|
||||
case ALL_FLOATS: {
|
||||
// All values can be exactly represented as floats - use float vector
|
||||
fl::vector<float> floatData;
|
||||
for (const auto& item : arr) {
|
||||
if (item.is<double>()) {
|
||||
floatData.push_back(static_cast<float>(item.as<double>()));
|
||||
} else {
|
||||
int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
|
||||
floatData.push_back(static_cast<float>(val));
|
||||
}
|
||||
}
|
||||
return fl::make_shared<JsonValue>(fl::move(floatData));
|
||||
}
|
||||
case GENERIC_ARRAY:
|
||||
default: {
|
||||
// No optimization possible - use regular array
|
||||
JsonArray regularArr;
|
||||
for (const auto& item : arr) {
|
||||
regularArr.push_back(convert(item));
|
||||
}
|
||||
return fl::make_shared<JsonValue>(fl::move(regularArr));
|
||||
}
|
||||
}
|
||||
} else if (src.is<FLArduinoJson::JsonObjectConst>()) {
|
||||
JsonObject obj;
|
||||
for (const auto& kv : src.as<FLArduinoJson::JsonObjectConst>()) {
|
||||
obj[fl::string(kv.key().c_str())] = convert(kv.value());
|
||||
}
|
||||
return fl::make_shared<JsonValue>(fl::move(obj));
|
||||
}
|
||||
return fl::make_shared<JsonValue>(nullptr); // Should not happen
|
||||
}
|
||||
};
|
||||
|
||||
return Converter::convert(doc.as<FLArduinoJson::JsonVariantConst>());
|
||||
#endif
|
||||
}
|
||||
|
||||
fl::string JsonValue::to_string() const {
|
||||
// Parse the JSON value to a string, then parse it back to a Json object,
|
||||
// and use the working to_string_native method
|
||||
// This is a workaround to avoid reimplementing the serialization logic
|
||||
|
||||
// First, create a temporary Json document with this value
|
||||
Json temp;
|
||||
// Use the public method to set the value
|
||||
temp.set_value(fl::make_shared<JsonValue>(*this));
|
||||
// Use the working implementation
|
||||
return temp.to_string_native();
|
||||
}
|
||||
|
||||
fl::string Json::to_string_native() const {
|
||||
if (!m_value) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
// 🚨 NEW APPROACH: Use fl::deque for memory-efficient JSON serialization
|
||||
// This avoids memory fragmentation and bypasses broken ArduinoJson integration
|
||||
fl::deque<char> json_chars;
|
||||
|
||||
// Helper lambda for appending strings to deque
|
||||
auto append_string = [&json_chars](const char* str) {
|
||||
while (*str) {
|
||||
json_chars.push_back(*str);
|
||||
++str;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper lambda for appending fl::string to deque
|
||||
auto append_fl_string = [&json_chars](const fl::string& str) {
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
json_chars.push_back(str[i]);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper lambda for appending escaped strings
|
||||
auto append_escaped_string = [&](const fl::string& str) {
|
||||
json_chars.push_back('"');
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
char c = str[i];
|
||||
switch (c) {
|
||||
case '"': append_string("\\\""); break;
|
||||
case '\\': append_string("\\\\"); break;
|
||||
case '\n': append_string("\\n"); break;
|
||||
case '\r': append_string("\\r"); break;
|
||||
case '\t': append_string("\\t"); break;
|
||||
case '\b': append_string("\\b"); break;
|
||||
case '\f': append_string("\\f"); break;
|
||||
default:
|
||||
json_chars.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
json_chars.push_back('"');
|
||||
};
|
||||
|
||||
// Recursive function to serialize JsonValue to deque
|
||||
fl::function<void(const JsonValue&)> serialize_value = [&](const JsonValue& value) {
|
||||
if (value.is_null()) {
|
||||
append_string("null");
|
||||
} else if (value.is_bool()) {
|
||||
auto opt = value.as_bool();
|
||||
if (opt) {
|
||||
append_string(*opt ? "true" : "false");
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_int()) {
|
||||
auto opt = value.as_int();
|
||||
if (opt) {
|
||||
fl::string num_str;
|
||||
num_str.append(*opt);
|
||||
append_fl_string(num_str);
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_float()) {
|
||||
auto opt = value.as_float();
|
||||
if (opt) {
|
||||
fl::string num_str;
|
||||
// Use fl::string::append which already handles float formatting correctly
|
||||
//num_str.append(*opt);
|
||||
num_str.append(static_cast<float>(*opt), 3);
|
||||
append_fl_string(num_str);
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_string()) {
|
||||
auto opt = value.as_string();
|
||||
if (opt) {
|
||||
append_escaped_string(*opt);
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_array()) {
|
||||
auto opt = value.as_array();
|
||||
if (opt) {
|
||||
json_chars.push_back('[');
|
||||
bool first = true;
|
||||
for (const auto& item : *opt) {
|
||||
if (!first) {
|
||||
json_chars.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
if (item) {
|
||||
serialize_value(*item);
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
}
|
||||
json_chars.push_back(']');
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_object()) {
|
||||
auto opt = value.as_object();
|
||||
if (opt) {
|
||||
json_chars.push_back('{');
|
||||
bool first = true;
|
||||
for (const auto& kv : *opt) {
|
||||
if (!first) {
|
||||
json_chars.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
append_escaped_string(kv.first);
|
||||
json_chars.push_back(':');
|
||||
if (kv.second) {
|
||||
serialize_value(*kv.second);
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
}
|
||||
json_chars.push_back('}');
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else {
|
||||
// Handle specialized array types
|
||||
if (value.is_audio()) {
|
||||
auto audioOpt = value.as_audio();
|
||||
if (audioOpt) {
|
||||
json_chars.push_back('[');
|
||||
bool first = true;
|
||||
for (const auto& item : *audioOpt) {
|
||||
if (!first) {
|
||||
json_chars.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
fl::string num_str;
|
||||
num_str.append(static_cast<int>(item));
|
||||
append_fl_string(num_str);
|
||||
}
|
||||
json_chars.push_back(']');
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_bytes()) {
|
||||
auto bytesOpt = value.as_bytes();
|
||||
if (bytesOpt) {
|
||||
json_chars.push_back('[');
|
||||
bool first = true;
|
||||
for (const auto& item : *bytesOpt) {
|
||||
if (!first) {
|
||||
json_chars.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
fl::string num_str;
|
||||
num_str.append(static_cast<int>(item));
|
||||
append_fl_string(num_str);
|
||||
}
|
||||
json_chars.push_back(']');
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else if (value.is_floats()) {
|
||||
auto floatsOpt = value.as_floats();
|
||||
if (floatsOpt) {
|
||||
json_chars.push_back('[');
|
||||
bool first = true;
|
||||
for (const auto& item : *floatsOpt) {
|
||||
if (!first) {
|
||||
json_chars.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
fl::string num_str;
|
||||
num_str.append(static_cast<float>(item), 6);
|
||||
append_fl_string(num_str);
|
||||
}
|
||||
json_chars.push_back(']');
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
} else {
|
||||
append_string("null");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Serialize the root value
|
||||
serialize_value(*m_value);
|
||||
|
||||
// Convert deque to fl::string efficiently
|
||||
fl::string result;
|
||||
if (!json_chars.empty()) {
|
||||
// Get pointer to the underlying data and construct string directly
|
||||
result.assign(&json_chars[0], json_chars.size());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Forward declaration for the serializeValue function
|
||||
fl::string serializeValue(const JsonValue& value);
|
||||
|
||||
|
||||
fl::string Json::normalizeJsonString(const char* jsonStr) {
|
||||
fl::string result;
|
||||
if (!jsonStr) {
|
||||
return result;
|
||||
}
|
||||
size_t len = strlen(jsonStr);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
char c = jsonStr[i];
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
2208
libraries/FastLED/src/fl/json.h
Normal file
2208
libraries/FastLED/src/fl/json.h
Normal file
File diff suppressed because it is too large
Load Diff
47
libraries/FastLED/src/fl/leds.cpp
Normal file
47
libraries/FastLED/src/fl/leds.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
#include "fl/leds.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
Leds::Leds(CRGB *leds, const XYMap &xymap) : mXyMap(xymap), mLeds(leds) {}
|
||||
|
||||
CRGB &Leds::operator()(int x, int y) {
|
||||
if (!mXyMap.has(x, y)) {
|
||||
return empty();
|
||||
}
|
||||
return mLeds[mXyMap(x, y)];
|
||||
}
|
||||
|
||||
CRGB &Leds::empty() {
|
||||
static CRGB empty_led;
|
||||
return empty_led;
|
||||
}
|
||||
|
||||
const CRGB &Leds::operator()(int x, int y) const {
|
||||
if (!mXyMap.has(x, y)) {
|
||||
return empty();
|
||||
}
|
||||
return mLeds[mXyMap(x, y)];
|
||||
}
|
||||
|
||||
CRGB *Leds::operator[](int y) {
|
||||
FASTLED_ASSERT(mXyMap.isSerpentine() || mXyMap.isLineByLine(),
|
||||
"XYMap is not serpentine or line by line");
|
||||
return &mLeds[mXyMap(0, y)];
|
||||
}
|
||||
const CRGB *Leds::operator[](int y) const {
|
||||
FASTLED_ASSERT(mXyMap.isSerpentine() || mXyMap.isLineByLine(),
|
||||
"XYMap is not serpentine or line by line");
|
||||
return &mLeds[mXyMap(0, y)];
|
||||
}
|
||||
|
||||
Leds::Leds(CRGB *leds, u16 width, u16 height)
|
||||
: Leds(leds, XYMap::constructRectangularGrid(width, height)) {}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
76
libraries/FastLED/src/fl/leds.h
Normal file
76
libraries/FastLED/src/fl/leds.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// Leds definition.
|
||||
// Drawing operations on a block of leds requires information about the layout
|
||||
// of the leds. Hence this class.
|
||||
class Leds {
|
||||
public:
|
||||
Leds(CRGB *leds, u16 width, u16 height);
|
||||
Leds(CRGB *leds, const XYMap &xymap);
|
||||
|
||||
// Copy constructor and assignment operator.
|
||||
Leds(const Leds &) = default;
|
||||
Leds &operator=(const Leds &) = default;
|
||||
Leds(Leds &&) = default;
|
||||
|
||||
// out of bounds access returns empty() led and is safe to read/write.
|
||||
CRGB &operator()(int x, int y);
|
||||
const CRGB &operator()(int x, int y) const;
|
||||
|
||||
CRGB &at(int x, int y) { return (*this)(x, y); }
|
||||
const CRGB &at(int x, int y) const { return (*this)(x, y); }
|
||||
|
||||
fl::size width() const { return mXyMap.getHeight(); }
|
||||
fl::size height() const { return mXyMap.getWidth(); }
|
||||
|
||||
// Allows normal matrix array (row major) access, bypassing the XYMap.
|
||||
// Will assert if XYMap is not serpentine or line by line.
|
||||
CRGB *operator[](int x);
|
||||
const CRGB *operator[](int x) const;
|
||||
// Raw data access.
|
||||
CRGB *rgb() { return mLeds; }
|
||||
const CRGB *rgb() const { return mLeds; }
|
||||
|
||||
const XYMap &xymap() const { return mXyMap; }
|
||||
|
||||
operator CRGB *() { return mLeds; }
|
||||
operator const CRGB *() const { return mLeds; }
|
||||
|
||||
void fill(const CRGB &color) {
|
||||
for (fl::size i = 0; i < mXyMap.getTotal(); ++i) {
|
||||
mLeds[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
static CRGB &empty(); // Allows safe out of bounds access.
|
||||
XYMap mXyMap;
|
||||
CRGB *mLeds;
|
||||
};
|
||||
|
||||
template <fl::size W, fl::size H> class LedsXY : public Leds {
|
||||
public:
|
||||
LedsXY() : Leds(mLedsData, XYMap::constructSerpentine(W, H)) {}
|
||||
explicit LedsXY(bool is_serpentine)
|
||||
: Leds(mLedsData, is_serpentine ? XYMap::constructSerpentine(W, H)
|
||||
: XYMap::constructRectangularGrid(W, H)) {}
|
||||
LedsXY(const LedsXY &) = default;
|
||||
LedsXY &operator=(const LedsXY &) = default;
|
||||
void setXyMap(const XYMap &xymap) { mXyMap = xymap; }
|
||||
void setSerpentine(bool is_serpentine) {
|
||||
mXyMap = is_serpentine ? XYMap::constructSerpentine(W, H)
|
||||
: XYMap::constructRectangularGrid(W, H);
|
||||
}
|
||||
|
||||
private:
|
||||
CRGB mLedsData[W * H] = {};
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
26
libraries/FastLED/src/fl/line_simplification.cpp
Normal file
26
libraries/FastLED/src/fl/line_simplification.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
/*
|
||||
Douglas-Peucker line simplification algorithm.
|
||||
*/
|
||||
|
||||
#include "fl/line_simplification.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace /*compiled_test*/ {
|
||||
|
||||
// // LineSimplifier<float>::LineSimplifier() : epsilon(0.0) {}
|
||||
// using LineSimplifierF = LineSimplifier<float>;
|
||||
// using LineSimplifierD = LineSimplifier<double>;
|
||||
|
||||
// LineSimplifierF s_test;
|
||||
// LineSimplifierD s_testd;
|
||||
|
||||
// void foo() {
|
||||
// fl::vector<vec2<float>> points;
|
||||
// s_test.simplifyInplace(&points);
|
||||
// }
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace fl
|
||||
364
libraries/FastLED/src/fl/line_simplification.h
Normal file
364
libraries/FastLED/src/fl/line_simplification.h
Normal file
@@ -0,0 +1,364 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
|
||||
Line simplification based of an improved Douglas-Peucker algorithm with only
|
||||
O(n) extra memory. Memory structures are inlined so that most simplifications
|
||||
can be done with zero heap allocations.
|
||||
|
||||
There are two versions here, one that simplifies using a threshold, and another
|
||||
version which will simplify to an exact number of points, however the latter is
|
||||
expensive since it must re-run the algorithm multiple times to find the right
|
||||
threshold. The first version is much faster and should be used in most cases.
|
||||
|
||||
*/
|
||||
|
||||
#include "fl/bitset.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/point.h"
|
||||
#include "fl/span.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
template <typename NumberT = float> class LineSimplifier {
|
||||
public:
|
||||
// This line simplification algorithm will remove vertices that are close
|
||||
// together upto a distance of mMinDistance. The algorithm is based on the
|
||||
// Douglas-Peucker but with some tweaks for memory efficiency. Most common
|
||||
// usage of this class for small sized inputs (~20) will produce no heap
|
||||
// allocations.
|
||||
using Point = fl::vec2<NumberT>;
|
||||
using VectorPoint = fl::vector<Point>;
|
||||
|
||||
LineSimplifier() : mMinDistance(EPSILON_F) {}
|
||||
LineSimplifier(const LineSimplifier &other) = default;
|
||||
LineSimplifier &operator=(const LineSimplifier &other) = default;
|
||||
LineSimplifier(LineSimplifier &&other) = default;
|
||||
LineSimplifier &operator=(LineSimplifier &&other) = default;
|
||||
|
||||
explicit LineSimplifier(NumberT e) : mMinDistance(e) {}
|
||||
void setMinimumDistance(NumberT eps) { mMinDistance = eps; }
|
||||
|
||||
// simplifyInPlace.
|
||||
void simplifyInplace(fl::vector<Point> *polyline) {
|
||||
simplifyInplaceT(polyline);
|
||||
}
|
||||
template <typename VectorType> void simplifyInplace(VectorType *polyLine) {
|
||||
simplifyInplaceT(polyLine);
|
||||
}
|
||||
|
||||
// simplify to the output vector.
|
||||
void simplify(const fl::span<const Point> &polyLine,
|
||||
fl::vector<Point> *out) {
|
||||
simplifyT(polyLine, out);
|
||||
}
|
||||
template <typename VectorType>
|
||||
void simplify(const fl::span<Point> &polyLine, VectorType *out) {
|
||||
simplifyT(polyLine, out);
|
||||
}
|
||||
|
||||
template <typename VectorType>
|
||||
static void removeOneLeastError(VectorType *_poly) {
|
||||
bitset<256> keep;
|
||||
VectorType &poly = *_poly;
|
||||
keep.assign(poly.size(), 1);
|
||||
const int n = poly.size();
|
||||
NumberT bestErr = INFINITY_DOUBLE;
|
||||
int bestIdx = -1;
|
||||
|
||||
// scan all interior “alive” points
|
||||
for (int i = 1; i + 1 < n; ++i) {
|
||||
if (!keep[i])
|
||||
continue;
|
||||
|
||||
// find previous alive
|
||||
int L = i - 1;
|
||||
while (L >= 0 && !keep[L])
|
||||
--L;
|
||||
// find next alive
|
||||
int R = i + 1;
|
||||
while (R < n && !keep[R])
|
||||
++R;
|
||||
|
||||
if (L < 0 || R >= n)
|
||||
continue; // endpoints
|
||||
|
||||
// compute perp‐distance² to the chord L→R
|
||||
NumberT dx = poly[R].x - poly[L].x;
|
||||
NumberT dy = poly[R].y - poly[L].y;
|
||||
NumberT vx = poly[i].x - poly[L].x;
|
||||
NumberT vy = poly[i].y - poly[L].y;
|
||||
NumberT len2 = dx * dx + dy * dy;
|
||||
NumberT err =
|
||||
(len2 > NumberT(0))
|
||||
? ((dx * vy - dy * vx) * (dx * vy - dy * vx) / len2)
|
||||
: (vx * vx + vy * vy);
|
||||
|
||||
if (err < bestErr) {
|
||||
bestErr = err;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// now “remove” that one point
|
||||
if (bestIdx >= 0)
|
||||
// keep[bestIdx] = 0;
|
||||
poly.erase(poly.begin() + bestIdx);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename VectorType> void simplifyInplaceT(VectorType *polyLine) {
|
||||
// run the simplification algorithm
|
||||
span<Point> slice(polyLine->data(), polyLine->size());
|
||||
simplifyT(slice, polyLine);
|
||||
}
|
||||
|
||||
template <typename VectorType>
|
||||
void simplifyT(const fl::span<const Point> &polyLine, VectorType *out) {
|
||||
// run the simplification algorithm
|
||||
simplifyInternal(polyLine);
|
||||
|
||||
// copy the result to the output slice
|
||||
out->assign(mSimplified.begin(), mSimplified.end());
|
||||
}
|
||||
// Runs in O(n) allocations: one bool‐array + one index stack + one output
|
||||
// vector
|
||||
void simplifyInternal(const fl::span<const Point> &polyLine) {
|
||||
mSimplified.clear();
|
||||
int n = polyLine.size();
|
||||
if (n < 2) {
|
||||
if (n) {
|
||||
mSimplified.assign(polyLine.data(), polyLine.data() + n);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const NumberT minDist2 = mMinDistance * mMinDistance;
|
||||
|
||||
// mark all points as “kept” initially
|
||||
keep.assign(n, 1);
|
||||
|
||||
// explicit stack of (start,end) index pairs
|
||||
indexStack.clear();
|
||||
// indexStack.reserve(64);
|
||||
indexStack.push_back({0, n - 1});
|
||||
|
||||
// process segments
|
||||
while (!indexStack.empty()) {
|
||||
// auto [i0, i1] = indexStack.back();
|
||||
auto pair = indexStack.back();
|
||||
int i0 = pair.first;
|
||||
int i1 = pair.second;
|
||||
indexStack.pop_back();
|
||||
const bool has_interior = (i1 - i0) > 1;
|
||||
if (!has_interior) {
|
||||
// no interior points, just keep the endpoints
|
||||
// keep[i0] = 1;
|
||||
// keep[i1] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// find farthest point in [i0+1 .. i1-1]
|
||||
NumberT maxDist2 = 0;
|
||||
int split = i0;
|
||||
for (int i = i0 + 1; i < i1; ++i) {
|
||||
if (!keep[i])
|
||||
continue;
|
||||
NumberT d2 = PerpendicularDistance2(polyLine[i], polyLine[i0],
|
||||
polyLine[i1]);
|
||||
|
||||
// FASTLED_WARN("Perpendicular distance2 between "
|
||||
// << polyLine[i] << " and " << polyLine[i0]
|
||||
// << " and " << polyLine[i1] << " is " << d2);
|
||||
|
||||
if (d2 > maxDist2) {
|
||||
maxDist2 = d2;
|
||||
split = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDist2 > minDist2) {
|
||||
// need to keep that split point and recurse on both halves
|
||||
indexStack.push_back({i0, split});
|
||||
indexStack.push_back({split, i1});
|
||||
} else {
|
||||
// drop all interior points in this segment
|
||||
for (int i = i0 + 1; i < i1; ++i) {
|
||||
keep[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect survivors
|
||||
mSimplified.clear();
|
||||
mSimplified.reserve(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (keep[i])
|
||||
mSimplified.push_back(polyLine[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NumberT mMinDistance;
|
||||
|
||||
// workspace buffers
|
||||
fl::bitset<256> keep; // marks which points survive
|
||||
fl::vector_inlined<fl::pair<int, int>, 64>
|
||||
indexStack; // manual recursion stack
|
||||
VectorPoint mSimplified; // output buffer
|
||||
|
||||
static NumberT PerpendicularDistance2(const Point &pt, const Point &a,
|
||||
const Point &b) {
|
||||
// vector AB
|
||||
NumberT dx = b.x - a.x;
|
||||
NumberT dy = b.y - a.y;
|
||||
// vector AP
|
||||
NumberT vx = pt.x - a.x;
|
||||
NumberT vy = pt.y - a.y;
|
||||
|
||||
// squared length of AB
|
||||
NumberT len2 = dx * dx + dy * dy;
|
||||
if (len2 <= NumberT(0)) {
|
||||
// A and B coincide — just return squared dist from A to P
|
||||
return vx * vx + vy * vy;
|
||||
}
|
||||
|
||||
// cross‐product magnitude (AB × AP) in 2D is (dx*vy − dy*vx)
|
||||
NumberT cross = dx * vy - dy * vx;
|
||||
// |cross|/|AB| is the perpendicular distance; we want squared:
|
||||
return (cross * cross) / len2;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename NumberT = float> class LineSimplifierExact {
|
||||
public:
|
||||
LineSimplifierExact() = default;
|
||||
using Point = vec2<NumberT>;
|
||||
|
||||
LineSimplifierExact(int count) : mCount(count) {}
|
||||
|
||||
void setCount(u32 count) { mCount = count; }
|
||||
|
||||
template <typename VectorType = fl::vector<Point>>
|
||||
void simplifyInplace(VectorType *polyLine) {
|
||||
return simplify(*polyLine, polyLine);
|
||||
}
|
||||
|
||||
template <typename VectorType = fl::vector<Point>>
|
||||
void simplify(const fl::span<const Point> &polyLine, VectorType *out) {
|
||||
if (mCount > polyLine.size()) {
|
||||
safeCopy(polyLine, out);
|
||||
return;
|
||||
} else if (mCount == polyLine.size()) {
|
||||
safeCopy(polyLine, out);
|
||||
return;
|
||||
} else if (mCount < 2) {
|
||||
fl::vector_fixed<Point, 2> temp;
|
||||
if (polyLine.size() > 0) {
|
||||
temp.push_back(polyLine[0]);
|
||||
}
|
||||
if (polyLine.size() > 1) {
|
||||
temp.push_back(polyLine[polyLine.size() - 1]);
|
||||
}
|
||||
out->assign(temp.begin(), temp.end());
|
||||
return;
|
||||
}
|
||||
NumberT est_max_dist = estimateMaxDistance(polyLine);
|
||||
NumberT min = 0;
|
||||
NumberT max = est_max_dist;
|
||||
NumberT mid = (min + max) / 2.0f;
|
||||
while (true) {
|
||||
// min < max;
|
||||
auto diff = max - min;
|
||||
const bool done = (diff < 0.01f);
|
||||
out->clear();
|
||||
mLineSimplifier.setMinimumDistance(mid);
|
||||
mLineSimplifier.simplify(polyLine, out);
|
||||
|
||||
fl::size n = out->size();
|
||||
|
||||
if (n == mCount) {
|
||||
return; // we are done
|
||||
}
|
||||
|
||||
// Handle the last few iterations manually. Often the algo will get
|
||||
// stuck here.
|
||||
if (n == mCount + 1) {
|
||||
// Just one more left, so peel it off.
|
||||
mLineSimplifier.removeOneLeastError(out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == mCount + 2) {
|
||||
// Just two more left, so peel them off.
|
||||
mLineSimplifier.removeOneLeastError(out);
|
||||
mLineSimplifier.removeOneLeastError(out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
while (out->size() > mCount) {
|
||||
// we have too many points, so we need to increase the
|
||||
// distance
|
||||
mLineSimplifier.removeOneLeastError(out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (out->size() < mCount) {
|
||||
max = mid;
|
||||
} else {
|
||||
min = mid;
|
||||
}
|
||||
mid = (min + max) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static NumberT estimateMaxDistance(const fl::span<const Point> &polyLine) {
|
||||
// Rough guess: max distance between endpoints
|
||||
if (polyLine.size() < 2)
|
||||
return 0;
|
||||
|
||||
const Point &first = polyLine[0];
|
||||
const Point &last = polyLine[polyLine.size() - 1];
|
||||
NumberT dx = last.x - first.x;
|
||||
NumberT dy = last.y - first.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
template <typename VectorType>
|
||||
void safeCopy(const fl::span<const Point> &polyLine, VectorType *out) {
|
||||
auto *first_out = out->data();
|
||||
// auto* last_out = first_out + mCount;
|
||||
auto *other_first_out = polyLine.data();
|
||||
// auto* other_last_out = other_first_out + polyLine.size();
|
||||
const bool is_same = first_out == other_first_out;
|
||||
if (is_same) {
|
||||
return;
|
||||
}
|
||||
auto *last_out = first_out + mCount;
|
||||
auto *other_last_out = other_first_out + polyLine.size();
|
||||
|
||||
const bool is_overlapping =
|
||||
(first_out >= other_first_out && first_out < other_last_out) ||
|
||||
(other_first_out >= first_out && other_first_out < last_out);
|
||||
|
||||
if (!is_overlapping) {
|
||||
out->assign(polyLine.data(), polyLine.data() + polyLine.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate a temporary buffer
|
||||
fl::vector_inlined<Point, 64> temp;
|
||||
temp.assign(polyLine.begin(), polyLine.end());
|
||||
out->assign(temp.begin(), temp.end());
|
||||
return;
|
||||
}
|
||||
|
||||
u32 mCount = 10;
|
||||
LineSimplifier<NumberT> mLineSimplifier;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user