diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 8fdbcff2e5..56b63d4386 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -63,6 +63,8 @@ body: - ESP32-S2 - ESP32-C3 - Other + - ESP32-C6 (experimental) + - ESP32-C5 (experimental) validations: required: true - type: textarea diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bc1f9761a9..371f064cac 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -63,6 +63,7 @@ The build has two main phases: ### Code Validation - **No automated linting configured** - follow existing code style in files you edit - **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files +- **Language**: The repository language is English (british, american, canadian, or australian). If you find other languages, suggest a translation into English. - **C++ formatting available**: `clang-format` is installed but not in CI - **Always run tests before finishing**: `npm test` - **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below) @@ -85,7 +86,7 @@ wled00/ # Main firmware source (C++) │ ├── settings*.htm # Settings pages │ └── *.js/*.css # Frontend resources ├── *.cpp/*.h # Firmware source files - └── html_*.h # Generated embedded web files (DO NOT EDIT) + └── html_*.h # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT) tools/ # Build tools (Node.js) ├── cdata.js # Web UI build script └── cdata-test.js # Test suite @@ -101,7 +102,7 @@ package.json # Node.js dependencies and scripts - `wled00/wled.h` - Main firmware configuration - `platformio.ini` - Hardware build targets and settings -### Development Workflow +### Development Workflow (applies to agent mode only) 1. **For web UI changes**: - Edit files in `wled00/data/` - Run `npm run build` to regenerate headers @@ -148,10 +149,13 @@ package.json # Node.js dependencies and scripts ## Important Notes -- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated -- **Always commit both source files AND generated html_*.h files** -- **Web UI must be built before firmware compilation** +- **Always commit source files** +- **Web UI re-built is part of the platformio firmware compilation** +- **do not commit generated html_*.h files** +- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated. If needed, modify Web UI files in `wled00/data/`. - **Test web interface manually after any web UI changes** +- When reviewing a PR: the PR author does not need to update/commit generated html_*.h files - these files will be auto-generated when building the firmware binary. +- If updating Web UI files in `wled00/data/`, make use of common functions availeable in `wled00/data/common.js` where possible. - **Use VS Code with PlatformIO extension for best development experience** - **Hardware builds require appropriate ESP32/ESP8266 development board** diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ce35140b70..2d47aefe42 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -23,12 +23,13 @@ jobs: run: ls -la - name: "✏️ Generate release changelog" id: changelog - uses: janheinrichmerker/action-github-changelog-generator@v2.3 + uses: janheinrichmerker/action-github-changelog-generator@v2.4 with: token: ${{ secrets.GITHUB_TOKEN }} sinceTag: v0.15.0 + output: CHANGELOG_NIGHTLY.md # Exclude issues that were closed without resolution from changelog - exclude-labels: 'stale,wontfix,duplicate,invalid' + excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned' - name: Update Nightly Release uses: andelf/nightly-release@main env: @@ -37,7 +38,7 @@ jobs: tag_name: nightly name: 'Nightly Release $$' prerelease: true - body: ${{ steps.changelog.outputs.changelog }} + body_path: CHANGELOG_NIGHTLY.md files: | *.bin *.bin.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3b902dd94..59de4316cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,13 +20,13 @@ jobs: merge-multiple: true - name: "✏️ Generate release changelog" id: changelog - uses: janheinrichmerker/action-github-changelog-generator@v2.3 + uses: janheinrichmerker/action-github-changelog-generator@v2.4 with: token: ${{ secrets.GITHUB_TOKEN }} sinceTag: v0.15.0 maxIssues: 500 # Exclude issues that were closed without resolution from changelog - exclude-labels: 'stale,wontfix,duplicate,invalid' + excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned' - name: Create draft release uses: softprops/action-gh-release@v1 with: diff --git a/.gitignore b/.gitignore index ec9d4efcc3..62e72a9a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ compile_commands.json __pycache__/ +/.dummy +/dependencies.lock +/managed_components + esp01-update.sh platformio_override.ini replace_fs.py @@ -25,3 +29,4 @@ wled-update.sh /wled00/Release /wled00/wled00.ino.cpp /wled00/html_*.h +/wled00/js_*.h diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d73ba5b7d9..1cd77ecd14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,62 +1,177 @@ -## Thank you for making WLED better! +# Thank you for making WLED better! -Here are a few suggestions to make it easier for you to contribute! +WLED is a community-driven project, and every contribution matters! We appreciate your time and effort. -### Describe your PR +Our maintainers are here for two things: **helping you** improve your code, and **keeping WLED** lean, efficient, and maintainable. +We'll work with you to refine your contribution, but we'll also push back if something might create technical debt or add features without clear value. Don't take it personally - we're just protecting WLED's architecture while helping your contribution succeed! -Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing. +## Getting Started + +Here are a few suggestions to make it easier for you to contribute: + +### PR from a branch in your own fork +Start your pull request (PR) in a branch of your own fork. Don't make a PR directly from your main branch. +This lets you update your PR if needed, while you can work on other tasks in 'main' or in other branches. + +> [!TIP] +> **The easiest way to start your first PR** +> When viewing a file in `wled/WLED`, click on the "pen" icon and start making changes. +> When you choose to 'Commit changes', GitHub will automatically create a PR from your fork. +> +> image: fork and edit -A good description helps us to review and understand your proposed changes. For example, you could say a few words about -* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.) -* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code) -* testing you performed, known limitations, open ends you possibly could not solve. -* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉) ### Target branch for pull requests Please make all PRs against the `main` branch. +### Describing your PR + +Please add a description of your proposed code changes. +A PR with no description or just a few words might not get accepted, simply because very basic information is missing. +No need to write an essay! + +A good description helps us to review and understand your proposed changes. For example, you could say a few words about +* What you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.) +* How your code works (short technical summary - focus on important aspects that might not be obvious when reading the code) +* Testing you performed, known limitations, anything you couldn't quite solve. +* Let us know if you'd like guidance from a maintainer (WLED is a big project 😉) + +### Testing Your Changes + +Before submitting: + +- ✅ Does it compile? +- ✅ Does your feature/fix actually work? +- ✅ Did you break anything else? +- ✅ Tested on actual hardware if possible? + +Mention your testing in the PR description (e.g., "Tested on ESP32 + WS2812B"). + +## During Review + +We're all volunteers, so reviews can take some time (longer during busy times). +Don't worry - we haven't forgotten you! Feel free to ping after a week if there's no activity. + ### Updating your code -While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. -You can simply update your own branch, and push changes in response to reviewer recommendations. -Github will pick up the changes so your PR stays up-to-date. +While the PR is open, you can keep updating your branch - just push more commits! GitHub will automatically update your PR. -> [!CAUTION] +You don't need to squash commits or clean up history - we'll handle that when merging. + +> [!CAUTION] > Do not use "force-push" while your PR is open! -> It has many subtle and unexpected consequences on our github reposistory. -> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. +> It has many subtle and unexpected consequences on our GitHub repository. +> For example, we regularly lose review comments when the PR author force-pushes code changes. Our review bot (coderabbit) may become unable to properly track changes, it gets confused or stops responding to questions. +> So, pretty please, do not force-push. > [!TIP] -> use [cherry-picking](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop) to copy commits from one branch to another. +> Use [cherry-picking](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop) to copy commits from one branch to another. + + +### Responding to Reviews + +When we ask for changes: + +- **Add new commits** - please don't amend or force-push +- **Reply in the PR** - let us know when you've addressed comments +- **Ask questions** - if something's unclear, just ask! +- **Be patient** - we're all volunteers here 😊 + +You can reference feedback in commit messages: +> ```text +> Fix naming per @Aircoookie's suggestion +> ``` + +### Dealing with Merge Conflicts + +Got conflicts with `main`? No worries - here's how to fix them: + +**Using GitHub Desktop** (easier for beginners): -You can find a collection of very useful tips and tricks here: https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR +1. Click **Fetch origin**, then **Pull origin** +2. If conflicts exist, GitHub Desktop will warn you - click **View conflicts** +3. Open the conflicted files in your editor (VS Code, etc.) +4. Remove the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and keep the correct code +5. Save the files +6. Back in GitHub Desktop, commit the merge (it'll suggest a message) +7. Click **Push origin** + +**Using command line**: + + ```bash + git fetch origin + git merge origin/main + # Fix conflicts in your editor + git add . + git commit + git push + ``` + +Either way works fine - pick what you're comfortable with! Merging is simpler than rebasing and keeps everything connected. + +#### When you MUST rebase (really rare!) + +Sometimes you might hit merge conflicts with `main` that are harder to solve. Here's what to try: + +1. **Merge instead of rebase** (safest option): + ```bash + git fetch origin + git merge origin/main + git push + ``` + Keeps review comments attached and CI results visible! + +2. **Use cherry-picking** to copy commits between branches without rewriting history - [here's how](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop). + +3. **If all else fails, use `--force-with-lease`** (not plain `--force`): + ```bash + git rebase origin/main + git push --force-with-lease + ``` + Then **leave a comment** explaining why you had to force-push, and be ready to re-address some feedback. + +### Additional Resources +Want to know more? Check out: +- 📚 [GitHub Desktop documentation](https://docs.github.com/en/desktop) - if you prefer GUI tools +- 🎓 [How to properly submit a PR](https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR) - detailed tips and tricks + + +## After Approval +Once approved, a maintainer will merge your PR (possibly squashing commits). +Your contribution will be in the next WLED release - thank you! 🎉 + + +## Coding Guidelines ### Source Code from an AI agent or bot > [!IMPORTANT] -> Its OK if you took help from an AI for writing your source code. +> It's OK if you took help from an AI for writing your source code. > -> However, we expect a few things from you as the person making a contribution to WLED: -* Make sure you really understand the code suggested by the AI, and don't just accept it because it "seems to work". +> AI tools can be very helpful, but as the contributor, **you're responsible for the code**. + +* Make sure you really understand the AI-generated code, don't just accept it because it "seems to work". * Don't let the AI change existing code without double-checking by you as the contributor. Often, the result will not be complete. For example, previous source code comments may be lost. -* Remember that AI are still "Often-Wrong" ;-) -* If you don't feel very confident using English, you can use AI for translating code comments and descriptions into English. AI bots are very good at understanding language. However, always check if the results is correct. The translation might still have wrong technical terms, or errors in some details. +* Remember that AI is still "Often-Wrong" ;-) +* If you don't feel confident using English, you can use AI for translating code comments and descriptions into English. AI bots are very good at understanding language. However, always check if the results are correct. The translation might still have wrong technical terms, or errors in some details. + +#### Best Practice with AI -#### best practice with AI: - * As the person who contributes source code to WLED, make sure you understand exactly what the AI generated code does - * best practice: add a comment like ``'// below section of my code was generated by an AI``, when larger parts of your source code were not written by you personally. - * always review translations and code comments for correctness - * always review AI generated source code - * If the AI has rewritten existing code, check that the change is necessary and that nothing has been lost or broken. Also check that previous code comments are still intact. +AI tools are powerful but "often wrong" - your judgment is essential! 😊 +- ✅ **Understand the code** - As the person contributing to WLED, make sure you understand exactly what the AI-generated source code does +- ✅ **Review carefully** - AI can lose comments, introduce bugs, or make unnecessary changes +- ✅ **Be transparent** - Add a comment like `// This section was AI-generated` for larger chunks +- ✅ **Use AI for translation** - AI is great for translating comments to English (but verify technical terms!) ### Code style -When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) -Below are the guidelines we use in the WLED repository. +Don't stress too much about style! When in doubt, just match the style in the files you're editing. 😊 + +Here are our main guidelines: #### Indentation -We use tabs for Indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files. +We use tabs for indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files. You are all set if you have enabled `Editor: Detect Indentation` in VS Code. #### Blocks @@ -74,7 +189,7 @@ if (a == b) { if (a == b) doStuff(a); ``` -Acceptable - however the first variant is usually easier to read: +Also acceptable (though the first style is usually easier to read): ```cpp if (a == b) { @@ -105,20 +220,22 @@ if( a==b ){ #### Comments Comments should have a space between the delimiting characters (e.g. `//`) and the comment text. -Note: This is a recent change, the majority of the codebase still has comments without spaces. +We're gradually adopting this style - don't worry if you see older code without spaces! Good: -``` -// This is a comment. - -/* This is a CSS inline comment */ +```cpp +// This is a short inline comment. /* - * This is a comment + * This is a longer comment * wrapping over multiple lines, * used in WLED for file headers and function explanations */ - +``` +```css +/* This is a CSS inline comment */ +``` +```html ``` diff --git a/package-lock.json b/package-lock.json index 5a19be1d58..de0a4084f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "wled", - "version": "0.16.0-alpha", + "version": "16.0.0-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.16.0-alpha", + "version": "16.0.0-alpha", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", - "nodemon": "^3.1.9", + "nodemon": "^3.1.14", "web-resource-inliner": "^7.0.0" }, "engines": { @@ -111,10 +111,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -129,13 +132,15 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -211,12 +216,6 @@ "node": ">=14" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -524,15 +523,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -552,15 +554,15 @@ } }, "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", diff --git a/package.json b/package.json index 68d91b257d..840dc5006f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.16.0-alpha", + "version": "16.0.0-alpha", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { @@ -26,7 +26,7 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "web-resource-inliner": "^7.0.0", - "nodemon": "^3.1.9" + "nodemon": "^3.1.14" }, "engines": { "node": ">=20.0.0" diff --git a/pio-scripts/dynarray.py b/pio-scripts/dynarray.py new file mode 100644 index 0000000000..2d3cfa90c5 --- /dev/null +++ b/pio-scripts/dynarray.py @@ -0,0 +1,19 @@ +# Add a section to the linker script to store our dynamic arrays +# This is implemented as a pio post-script to ensure that we can +# place our linker script at the correct point in the command arguments. +Import("env") +from pathlib import Path + +platform = env.get("PIOPLATFORM") +script_file = Path(f"tools/dynarray_{platform}.ld") +if script_file.is_file(): + linker_script = f"-T{script_file}" + if platform == "espressif32": + # For ESP32, the script must be added at the right point in the list + linkflags = env.get("LINKFLAGS", []) + idx = linkflags.index("memory.ld") + linkflags.insert(idx+1, linker_script) + env.Replace(LINKFLAGS=linkflags) + else: + # For other platforms, put it in last + env.Append(LINKFLAGS=[linker_script]) diff --git a/pio-scripts/load_usermods.py b/pio-scripts/load_usermods.py index 38a08401e6..18852ff30b 100644 --- a/pio-scripts/load_usermods.py +++ b/pio-scripts/load_usermods.py @@ -1,6 +1,8 @@ Import('env') from collections import deque from pathlib import Path # For OS-agnostic path manipulation +import re +from urllib.parse import urlparse from click import secho from SCons.Script import Exit from platformio.builder.tools.piolib import LibBuilderBase @@ -25,25 +27,117 @@ def find_usermod(mod: str) -> Path: return mp raise RuntimeError(f"Couldn't locate module {mod} in usermods directory!") -def is_wled_module(dep: LibBuilderBase) -> bool: - """Returns true if the specified library is a wled module +# Names of external/registry deps listed in custom_usermods. +# Populated during parsing below; read by is_wled_module() at configure time. +_custom_usermod_names: set[str] = set() + +# Matches any RFC-valid URL scheme (http, https, git, git+https, symlink, file, hg+ssh, etc.) +_URL_SCHEME_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9+.-]*://') +# SSH git URL: user@host:path (e.g. git@github.com:user/repo.git#tag) +_SSH_URL_RE = re.compile(r'^[^@\s]+@[^@:\s]+:[^:\s]') +# Explicit custom name: "LibName = " (PlatformIO [=] form) +_NAME_EQ_RE = re.compile(r'^([A-Za-z0-9_.-]+)\s*=\s*(\S.*)') + + +def _is_external_entry(line: str) -> bool: + """Return True if line is a lib_deps-style external/registry entry.""" + if _NAME_EQ_RE.match(line): # "LibName = " + return True + if _URL_SCHEME_RE.match(line): # https://, git://, symlink://, etc. + return True + if _SSH_URL_RE.match(line): # git@github.com:user/repo.git + return True + if '@' in line: # "owner/Name @ ^1.0.0" + return True + if re.match(r'^[^/\s]+/[^/\s]+$', line): # "owner/Name" + return True + return False + + +def _predict_dep_name(entry: str) -> str | None: + """Predict the library name PlatformIO will assign to this dep (best-effort). + + Accuracy relies on the library's manifest "name" matching the repo/package + name in the spec. This holds for well-authored libraries; the libArchive + check (which requires library.json) provides an early-failure safety net. """ - return usermod_dir in Path(dep.src_dir).parents or str(dep.name).startswith("wled-") - -## Script starts here -# Process usermod option -usermods = env.GetProjectOption("custom_usermods","") + entry = entry.strip() + # "LibName = " — name is given explicitly; always use it + m = _NAME_EQ_RE.match(entry) + if m: + return m.group(1).strip() + # URL scheme: extract name from path + if _URL_SCHEME_RE.match(entry): + parsed = urlparse(entry) + if parsed.netloc in ('github.com', 'gitlab.com', 'bitbucket.com'): + parts = [p for p in parsed.path.split('/') if p] + if len(parts) >= 2: + name = parts[1] + else: + name = Path(parsed.path.rstrip('/')).name.strip() + if name.endswith('.git'): + name = name[:-4] + return name or None + # SSH git URL: git@github.com:user/repo.git#tag → repo + if _SSH_URL_RE.match(entry): + path_part = entry.split(':', 1)[1].split('#')[0].rstrip('/') + name = Path(path_part).name + return (name[:-4] if name.endswith('.git') else name) or None + # Versioned registry: "owner/Name @ version" → Name + if '@' in entry: + name_part = entry.split('@')[0].strip() + return name_part.split('/')[-1].strip() if '/' in name_part else name_part + # Plain registry: "owner/Name" → Name + if re.match(r'^[^/\s]+/[^/\s]+$', entry): + return entry.split('/')[-1].strip() + return None -# Handle "all usermods" case -if usermods == '*': - usermods = [f.name for f in usermod_dir.iterdir() if f.is_dir() and f.joinpath('library.json').exists()] -else: - usermods = usermods.split() -if usermods: - # Inject usermods in to project lib_deps - symlinks = [f"symlink://{find_usermod(mod).resolve()}" for mod in usermods] - env.GetProjectConfig().set("env:" + env['PIOENV'], 'lib_deps', env.GetProjectOption('lib_deps') + symlinks) +def is_wled_module(dep: LibBuilderBase) -> bool: + """Returns true if the specified library is a wled module.""" + return ( + usermod_dir in Path(dep.src_dir).parents + or str(dep.name).startswith("wled-") + or dep.name in _custom_usermod_names + ) + + +## Script starts here — parse custom_usermods +raw_usermods = env.GetProjectOption("custom_usermods", "") +usermods_libdeps: list[str] = [] + +for line in raw_usermods.splitlines(): + line = line.strip() + if not line or line.startswith('#') or line.startswith(';'): + continue + + if _is_external_entry(line): + # External URL or registry entry: pass through to lib_deps unchanged. + predicted = _predict_dep_name(line) + if predicted: + _custom_usermod_names.add(predicted) + else: + secho( + f"WARNING: Cannot determine library name for custom_usermods entry " + f"{line!r}. If it is not recognised as a WLED module at build time, " + f"ensure its library.json 'name' matches the repo name.", + fg="yellow", err=True) + usermods_libdeps.append(line) + else: + # Bare name(s): split on whitespace for backwards compatibility. + for token in line.split(): + if token == '*': + for mod_path in sorted(usermod_dir.iterdir()): + if mod_path.is_dir() and (mod_path / 'library.json').exists(): + _custom_usermod_names.add(mod_path.name) + usermods_libdeps.append(f"symlink://{mod_path.resolve()}") + else: + resolved = find_usermod(token) + _custom_usermod_names.add(resolved.name) + usermods_libdeps.append(f"symlink://{resolved.resolve()}") + +if usermods_libdeps: + env.GetProjectConfig().set("env:" + env['PIOENV'], 'lib_deps', env.GetProjectOption('lib_deps') + usermods_libdeps) # Utility function for assembling usermod include paths def cached_add_includes(dep, dep_cache: set, includes: deque): @@ -86,6 +180,14 @@ def wrapped_ConfigureProjectLibBuilder(xenv): # Add WLED's own dependencies for dir in extra_include_dirs: dep.env.PrependUnique(CPPPATH=str(dir)) + # Ensure debug info is emitted for this module's source files. + # validate_modules.py uses `nm --defined-only -l` on the final ELF to check + # that each module has at least one symbol placed in the binary. The -l flag + # reads DWARF debug sections to map placed symbols back to their original source + # files; without -g those sections are absent and the check cannot attribute any + # symbol to a specific module. We scope this to usermods only — the main WLED + # build and other libraries are unaffected. + dep.env.AppendUnique(CCFLAGS=["-g"]) # Enforce that libArchive is not set; we must link them directly to the executable if dep.lib_archive: broken_usermods.append(dep) @@ -93,9 +195,10 @@ def wrapped_ConfigureProjectLibBuilder(xenv): if broken_usermods: broken_usermods = [usermod.name for usermod in broken_usermods] secho( - f"ERROR: libArchive=false is missing on usermod(s) {' '.join(broken_usermods)} -- modules will not compile in correctly", - fg="red", - err=True) + f"ERROR: libArchive=false is missing on usermod(s) {' '.join(broken_usermods)} -- " + f"modules will not compile in correctly. Add '\"build\": {{\"libArchive\": false}}' " + f"to their library.json.", + fg="red", err=True) Exit(1) # Save the depbuilders list for later validation diff --git a/pio-scripts/validate_modules.py b/pio-scripts/validate_modules.py index d63b609ac8..ae098f43cc 100644 --- a/pio-scripts/validate_modules.py +++ b/pio-scripts/validate_modules.py @@ -1,16 +1,10 @@ +import os import re +import subprocess from pathlib import Path # For OS-agnostic path manipulation -from typing import Iterable from click import secho from SCons.Script import Action, Exit -from platformio.builder.tools.piolib import LibBuilderBase - - -def is_wled_module(env, dep: LibBuilderBase) -> bool: - """Returns true if the specified library is a wled module - """ - usermod_dir = Path(env["PROJECT_DIR"]).resolve() / "usermods" - return usermod_dir in Path(dep.src_dir).parents or str(dep.name).startswith("wled-") +Import("env") def read_lines(p: Path): @@ -19,29 +13,80 @@ def read_lines(p: Path): return f.readlines() -def check_map_file_objects(map_file: list[str], dirs: Iterable[str]) -> set[str]: - """ Identify which dirs contributed to the final build +def _get_nm_path(env) -> str: + """ Derive the nm tool path from the build environment """ + if "NM" in env: + return env.subst("$NM") + # Derive from the C compiler: xtensa-esp32-elf-gcc → xtensa-esp32-elf-nm + cc = env.subst("$CC") + nm = re.sub(r'(gcc|g\+\+)$', 'nm', os.path.basename(cc)) + return os.path.join(os.path.dirname(cc), nm) - Returns the (sub)set of dirs that are found in the output ELF - """ - # Pattern to match symbols in object directories - # Join directories into alternation - usermod_dir_regex = "|".join([re.escape(dir) for dir in dirs]) - # Matches nonzero address, any size, and any path in a matching directory - object_path_regex = re.compile(r"0x0*[1-9a-f][0-9a-f]*\s+0x[0-9a-f]+\s+\S+[/\\](" + usermod_dir_regex + r")[/\\]\S+\.o") +def check_elf_modules(elf_path: Path, env, module_lib_builders) -> set[str]: + """ Check which modules have at least one defined symbol placed in the ELF. + + The map file is not a reliable source for this: with LTO, original object + file paths are replaced by temporary ltrans.o partitions in all output + sections, making per-module attribution impossible from the map alone. + Instead we invoke nm --defined-only -l on the ELF, which uses DWARF debug + info to attribute each placed symbol to its original source file. + + Requires usermod libraries to be compiled with -g so that DWARF sections + are present in the ELF. load_usermods.py injects -g for all WLED modules + via dep.env.AppendUnique(CCFLAGS=["-g"]). + + Returns the set of build_dir basenames for confirmed modules. + """ + nm_path = _get_nm_path(env) + try: + result = subprocess.run( + [nm_path, "--defined-only", "-l", str(elf_path)], + capture_output=True, text=True, errors="ignore", timeout=120, + ) + nm_output = result.stdout + except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e: + secho(f"WARNING: nm failed ({e}); skipping per-module validation", fg="yellow", err=True) + return {Path(b.build_dir).name for b in module_lib_builders} # conservative pass + + # Match placed symbols against builders as we parse nm output, exiting early + # once all builders are accounted for. + # nm --defined-only still includes debugging symbols (type 'N') such as the + # per-CU markers GCC emits in .debug_info (e.g. "usermod_example_cpp_6734d48d"). + # These live at address 0x00000000 in their debug section — not in any load + # segment — so filtering them out leaves only genuinely placed symbols. + # nm -l appends a tab-separated "file:lineno" location to each symbol line. + remaining = {Path(str(b.src_dir)): Path(b.build_dir).name for b in module_lib_builders} found = set() - for line in map_file: - matches = object_path_regex.findall(line) - for m in matches: - found.add(m) + + for line in nm_output.splitlines(): + if not remaining: + break # all builders matched + addr, _, _ = line.partition(' ') + if not addr.lstrip('0'): + continue # zero address — skip debug-section marker + if '\t' not in line: + continue + loc = line.rsplit('\t', 1)[1] + # Strip trailing :lineno (e.g. "/path/to/foo.cpp:42" → "/path/to/foo.cpp") + src_path = Path(loc.rsplit(':', 1)[0]) + # Path.is_relative_to() handles OS-specific separators correctly without + # any regex, avoiding Windows path escaping issues. + for src_dir in list(remaining): + if src_path.is_relative_to(src_dir): + found.add(remaining.pop(src_dir)) + break + return found +DYNARRAY_SECTION = ".dtors" if env.get("PIOPLATFORM") == "espressif8266" else ".dynarray" +USERMODS_SECTION = f"{DYNARRAY_SECTION}.usermods.1" + def count_usermod_objects(map_file: list[str]) -> int: """ Returns the number of usermod objects in the usermod list """ # Count the number of entries in the usermods table section - return len([x for x in map_file if ".dtors.tbl.usermods.1" in x]) + return len([x for x in map_file if USERMODS_SECTION in x]) def validate_map_file(source, target, env): @@ -65,16 +110,17 @@ def validate_map_file(source, target, env): usermod_object_count = count_usermod_objects(map_file_contents) secho(f"INFO: {usermod_object_count} usermod object entries") - confirmed_modules = check_map_file_objects(map_file_contents, modules.keys()) + elf_path = build_dir / env.subst("${PROGNAME}.elf") + confirmed_modules = check_elf_modules(elf_path, env, module_lib_builders) + missing_modules = [modname for mdir, modname in modules.items() if mdir not in confirmed_modules] if missing_modules: secho( - f"ERROR: No object files from {missing_modules} found in linked output!", + f"ERROR: No symbols from {missing_modules} found in linked output!", fg="red", err=True) Exit(1) return None -Import("env") env.Append(LINKFLAGS=[env.subst("-Wl,--Map=${BUILD_DIR}/${PROGNAME}.map")]) env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", Action(validate_map_file, cmdstr='Checking linked optional modules (usermods) in map file')) diff --git a/platformio.ini b/platformio.ini index ec73bc5658..1645a52727 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,6 +29,7 @@ default_envs = nodemcuv2 esp32S3_wroom2 esp32s3dev_16MB_opi esp32s3dev_8MB_opi + esp32s3dev_8MB_qspi esp32s3_4M_qspi usermods @@ -121,6 +122,7 @@ build_flags = -D DECODE_LG=true -DWLED_USE_MY_CONFIG -D WLED_PS_DONT_REPLACE_FX ; PS replacement FX are purely a flash memory saving feature, do not replace classic FX until we run out of flash + -D WLED_ENABLE_DMX build_unflags = @@ -134,6 +136,7 @@ extra_scripts = pre:pio-scripts/set_metadata.py post:pio-scripts/output_bins.py post:pio-scripts/strip-floats.py + post:pio-scripts/dynarray.py pre:pio-scripts/user_config_copy.py pre:pio-scripts/load_usermods.py pre:pio-scripts/build_ui.py @@ -290,8 +293,7 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) -platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8 platform_packages = build_unflags = ${common.build_unflags} build_flags = -g @@ -511,8 +513,10 @@ upload_speed = 921600 custom_usermods = audioreactive build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 + -D SR_DMTYPE=-1 -D AUDIOPIN=-1 -D I2S_SDPIN=-1 -D I2S_WSPIN=-1 -D I2S_CKPIN=-1 -D MCLK_PIN=-1 ;; force AR to not allocate any PINs at startup + -D DATA_PINS=4 ;; default led pin = 16 conflicts with pins used for ethernet + ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only => uncomment if your board uses ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 -; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only lib_deps = ${esp32.lib_deps} board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = dio @@ -595,6 +599,19 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32s3dev_8MB_qspi] +;; generic ESP32-S3 board with 8MB FLASH and PSRAM (memory_type: qio_qspi). Try this one if esp32s3dev_8MB_opi does not work on your board +extends = env:esp32s3dev_8MB_opi +board_build.arduino.memory_type = qio_qspi +board_build.flash_mode = qio +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_qspi\" + -D WLED_WATCHDOG_TIMEOUT=0 + ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + -D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + ;; -DLOLIN_WIFI_FIX ;; uncomment if you have WiFi connectivity problems +monitor_filters = esp32_exception_decoder + [env:esp32S3_wroom2] ;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 ;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 2fc9aacfb4..e840eb4334 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -538,9 +538,21 @@ monitor_filters = esp32_exception_decoder # 433MHz RF remote example for esp32dev [env:esp32dev_usermod_RF433] extends = env:esp32dev -build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 -lib_deps = ${env:esp32dev.lib_deps} - sui77/rc-switch @ 2.6.4 +custom_usermods = + ${env:esp32dev.custom_usermods} + RF433 + +# External usermod from a git repository. +# The library's `library.json` must include `"build": {"libArchive": false}`. +# The name PlatformIO assigns is taken from the library's `library.json` "name" field. +# If that name doesn't match the repo name in the URL, use the "LibName = URL" form +# shown in the commented-out line below to supply the name explicitly. +[env:esp32dev_external_usermod] +extends = env:esp32dev +custom_usermods = + ${env:esp32dev.custom_usermods} + https://github.com/wled/wled-usermod-example.git#main + # ------------------------------------------------------------------------------ # Hub75 examples diff --git a/tools/cdata.js b/tools/cdata.js index c9ae7eb659..5ae7088b3e 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -26,7 +26,7 @@ const packageJson = require("../package.json"); // Export functions for testing module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan }; -const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_edit.h", "wled00/html_pxmagic.h", "wled00/html_pixelforge.h", "wled00/html_settings.h", "wled00/html_other.h"] +const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_edit.h", "wled00/html_pxmagic.h", "wled00/html_pixelforge.h", "wled00/html_settings.h", "wled00/html_other.h", "wled00/js_iro.h", "wled00/js_omggif.h"] // \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset const wledBanner = ` @@ -257,6 +257,33 @@ writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'px writeHtmlGzipped("wled00/data/pixelforge/pixelforge.htm", "wled00/html_pixelforge.h", 'pixelforge', false); // do not inline css //writeHtmlGzipped("wled00/data/edit.htm", "wled00/html_edit.h", 'edit'); +writeChunks( + "wled00/data/", + [ + { + file: "iro.js", + name: "JS_iro", + method: "gzip", + filter: "plain", // no minification, it is already minified + mangle: (s) => s.replace(/^\/\*![\s\S]*?\*\//, '') // remove license comment at the top + } + ], + "wled00/js_iro.h" +); + +writeChunks( + "wled00/data/pixelforge", + [ + { + file: "omggif.js", + name: "JS_omggif", + method: "gzip", + filter: "js-minify", + mangle: (s) => s.replace(/^\/\*![\s\S]*?\*\//, '') // remove license comment at the top + } + ], + "wled00/js_omggif.h" +); writeChunks( "wled00/data", @@ -367,6 +394,12 @@ writeChunks( name: "PAGE_settings_pin", method: "gzip", filter: "html-minify" + }, + { + file: "settings_pininfo.htm", + name: "PAGE_settings_pininfo", + method: "gzip", + filter: "html-minify" } ], "wled00/html_settings.h" diff --git a/tools/dynarray_espressif32.ld b/tools/dynarray_espressif32.ld new file mode 100644 index 0000000000..70ce51f19c --- /dev/null +++ b/tools/dynarray_espressif32.ld @@ -0,0 +1,10 @@ +/* ESP32 linker script fragment to add dynamic array section to binary */ +SECTIONS +{ + .dynarray : + { + . = ALIGN(0x10); + KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*))) + } > default_rodata_seg +} +INSERT AFTER .flash.rodata; diff --git a/usermods/Analog_Clock/Analog_Clock.cpp b/usermods/Analog_Clock/Analog_Clock.cpp index d3a2b73b8d..3e0a1e99a6 100644 --- a/usermods/Analog_Clock/Analog_Clock.cpp +++ b/usermods/Analog_Clock/Analog_Clock.cpp @@ -3,7 +3,6 @@ /* * Usermod for analog clock */ -extern Timezone* tz; class AnalogClockUsermod : public Usermod { private: diff --git a/usermods/PS_Comet/PS_Comet.cpp b/usermods/PS_Comet/PS_Comet.cpp new file mode 100644 index 0000000000..c96ba2594e --- /dev/null +++ b/usermods/PS_Comet/PS_Comet.cpp @@ -0,0 +1,123 @@ +#include "wled.h" +#include "FXparticleSystem.h" + +unsigned long nextCometCreationTime = 0; + +#define FX_FALLBACK_STATIC { SEGMENT.fill(SEGCOLOR(0)); return; } +// Use UINT32_MAX - 1 for the "no comet" case so we can add 1 later and not have it overflow +#define NULL_INDEX UINT32_MAX - 1 + +/////////////////////// +// Effect Function // +/////////////////////// + +void mode_pscomet() { + ParticleSystem2D *PartSys = nullptr; + uint32_t i; + + if (SEGMENT.call == 0) { // Initialization + // Try to allocate one comet for every column + if (!initParticleSystem2D(PartSys, SEGMENT.vWidth())) { + FX_FALLBACK_STATIC; // Allocation failed or not 2D + } + PartSys->setMotionBlur(170); // Enable motion blur + PartSys->setParticleSize(0); // Allow small comets to be a single pixel wide + } + else { + PartSys = reinterpret_cast(SEGENV.data); // If not first call, use existing data + } + if (PartSys == nullptr || SEGMENT.vHeight() < 2 || SEGMENT.vWidth() < 2) { + FX_FALLBACK_STATIC; + } + + PartSys->updateSystem(); // Update system properties (dimensions and data pointers) + + auto has_fallen_off_screen = [PartSys](uint32_t particleIndex) { + return particleIndex < PartSys->numSources + ? PartSys->sources[particleIndex].source.y < PartSys->maxY * -1 + : true; + }; + + // This will be SEGMENT.vWidth() unless the particle system had insufficient memory + uint32_t numComets = PartSys->numSources; + // Pick a random column for a new comet to spawn, but reset it to null if it's not time yet or there's already a + // comet nearby + uint32_t chosenIndex = hw_random(numComets); + if ( + strip.now < nextCometCreationTime + || !has_fallen_off_screen(chosenIndex - 1) + || !has_fallen_off_screen(chosenIndex) + || !has_fallen_off_screen(chosenIndex + 1) + ) { + chosenIndex = NULL_INDEX; + } else { + uint16_t cometFrequencyDelay = 2040 - (SEGMENT.intensity << 3); + nextCometCreationTime = strip.now + cometFrequencyDelay + hw_random16(cometFrequencyDelay); + } + uint8_t canLargeCometSpawn = + // Slider 3 determines % of large comets with extra particle sources on their sides + SEGMENT.custom1 > hw_random8(254) + && chosenIndex != 0 + && chosenIndex != numComets - 1; + uint8_t fallingSpeed = 1 + (SEGMENT.speed >> 2); + + // Update the comets + for (i = 0; i < numComets; i++) { + auto& source = PartSys->sources[i]; + auto& sourceParticle = source.source; + + if (!has_fallen_off_screen(i)) { + // Active comets fall downwards and emit flames + sourceParticle.y -= fallingSpeed; + source.vy = (SEGMENT.speed >> 5) - fallingSpeed; // Emitting speed (upwards) + PartSys->flameEmit(PartSys->sources[i]); + continue; + } + + bool isChosenComet = i == chosenIndex; + bool isChosenSideComet = + canLargeCometSpawn && + (i == chosenIndex - 1 || i == chosenIndex + 1); + + // Chosen comets respawn at the top + if (isChosenComet || isChosenSideComet) { + // Map the comet index into an output pixel index + sourceParticle.x = i * PartSys->maxX / (SEGMENT.vWidth() - 1); + // Spawn a bit above the top to avoid popping into view + sourceParticle.y = PartSys->maxY + (2 * fallingSpeed); + if (isChosenComet) { + // Slider 4 controls comet length via particle lifetime and fire intensity adjustments + source.maxLife = 16 + (SEGMENT.custom2 >> 2); + source.minLife = source.maxLife >> 1; + sourceParticle.ttl = 16 - (SEGMENT.custom2 >> 4); + } else { + // Side comets have fixed length + source.maxLife = 18; + source.minLife = 14; + sourceParticle.ttl = 16; + // Shift side comets up by 1 pixel + sourceParticle.y += 2 * PartSys->maxY / (SEGMENT.vHeight() - 1); + } + } + } + + // Slider 4 controls comet length via particle lifetime and fire intensity adjustments + PartSys->updateFire(max(255U - SEGMENT.custom2, 45U)); +} +static const char _data_FX_MODE_PSCOMET[] PROGMEM = "PS Comet@Falling Speed,Comet Frequency,Large Comet Probability,Comet Length;;!;2;pal=35,sx=128,ix=255,c1=32,c2=128"; + +///////////////////// +// UserMod Class // +///////////////////// + +class PSCometUsermod : public Usermod { + public: + void setup() override { + strip.addEffect(255, &mode_pscomet, _data_FX_MODE_PSCOMET); + } + + void loop() override {} +}; + +static PSCometUsermod ps_comet; +REGISTER_USERMOD(ps_comet); diff --git a/usermods/PS_Comet/README.md b/usermods/PS_Comet/README.md new file mode 100644 index 0000000000..67395e290b --- /dev/null +++ b/usermods/PS_Comet/README.md @@ -0,0 +1,25 @@ +## Description + +A 2D falling comet effect similar to "Matrix" but with a fire particle simulation to enhance the comet trail visuals. Works with custom color palettes, defaulting to "Fire". Supports "small" and "large" comets which are 1px and 3px wide respectively. + +Demo: [https://imgur.com/a/i1v5WAy](https://imgur.com/a/i1v5WAy) + +## Installation + +To activate the usermod, add the following line to your platformio_override.ini +```ini +custom_usermods = ps_comet +``` +Or if you are already using a usermod, append ps_comet to the list +```ini +custom_usermods = audioreactive ps_comet +``` + +You should now see "PS Comet" appear in your effect list. + +## Parameters + +1. **Falling Speed** sets how fast the comets fall +2. **Comet Frequency** determines how many comets are on screen at a time +3. **Large Comet Probability** determines how often large 3px wide comets spawn +4. **Comet Length** sets how far comet trails stretch vertically \ No newline at end of file diff --git a/usermods/PS_Comet/library.json b/usermods/PS_Comet/library.json new file mode 100644 index 0000000000..d6f569387c --- /dev/null +++ b/usermods/PS_Comet/library.json @@ -0,0 +1,4 @@ +{ + "name": "PS Comet", + "build": { "libArchive": false } +} \ No newline at end of file diff --git a/usermods/Temperature/Temperature.cpp b/usermods/Temperature/Temperature.cpp index c86b9e9842..6dcaa70c6b 100644 --- a/usermods/Temperature/Temperature.cpp +++ b/usermods/Temperature/Temperature.cpp @@ -1,6 +1,6 @@ #include "UsermodTemperature.h" -static uint16_t mode_temperature(); +static void mode_temperature(); //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float UsermodTemperature::readDallas() { @@ -369,13 +369,12 @@ const char UsermodTemperature::_temperature[] PROGMEM = "temperature"; const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; const char UsermodTemperature::_data_fx[] PROGMEM = "Temperature@Min,Max;;!;01;pal=54,sx=255,ix=0"; -static uint16_t mode_temperature() { +static void mode_temperature() { float low = roundf(mapf((float)SEGMENT.speed, 0.f, 255.f, -150.f, 150.f)); // default: 15°C, range: -15°C to 15°C float high = roundf(mapf((float)SEGMENT.intensity, 0.f, 255.f, 300.f, 600.f)); // default: 30°C, range 30°C to 60°C float temp = constrain(UsermodTemperature::getInstance()->getTemperatureC()*10.f, low, high); // get a little better resolution (*10) unsigned i = map(roundf(temp), (unsigned)low, (unsigned)high, 0, 248); SEGMENT.fill(SEGMENT.color_from_palette(i, false, false, 255)); - return FRAMETIME; } diff --git a/usermods/TetrisAI_v2/TetrisAI_v2.cpp b/usermods/TetrisAI_v2/TetrisAI_v2.cpp index b51250b262..f9a86f3049 100644 --- a/usermods/TetrisAI_v2/TetrisAI_v2.cpp +++ b/usermods/TetrisAI_v2/TetrisAI_v2.cpp @@ -5,9 +5,12 @@ #include "tetrisaigame.h" // By: muebau +bool noFlashOnClear = false; + typedef struct TetrisAI_data { unsigned long lastTime = 0; + unsigned long clearingStartTime = 0; TetrisAIGame tetris; uint8_t intelligence; uint8_t rotate; @@ -31,16 +34,27 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) //GRID for (auto index_y = 4; index_y < tetris->grid.height; index_y++) { + bool isRowClearing = tetris->grid.gridBW.clearingRows[index_y]; for (auto index_x = 0; index_x < tetris->grid.width; index_x++) { CRGB color; - if (*tetris->grid.getPixel(index_x, index_y) == 0) - { + uint8_t gridPixel = *tetris->grid.getPixel(index_x, index_y); + if (isRowClearing) { + if (noFlashOnClear) { + color = CRGB::Gray; + } else { + //flash color white and black every 200ms + color = (strip.now % 200) < 150 + ? CRGB::Gray + : CRGB::Black; + } + } + else if (gridPixel == 0) { //BG color color = SEGCOLOR(1); } //game over animation - else if(*tetris->grid.getPixel(index_x, index_y) == 254) + else if (gridPixel == 254) { //use fg color = SEGCOLOR(0); @@ -48,7 +62,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) else { //spread the color over the whole palette - uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32; + uint8_t colorIndex = gridPixel * 32; colorIndex += tetrisai_data->colorOffset; color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND); } @@ -98,13 +112,13 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) //////////////////////////// // 2D Tetris AI // //////////////////////////// -uint16_t mode_2DTetrisAI() +void mode_2DTetrisAI() { if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data))) { // not a 2D set-up SEGMENT.fill(SEGCOLOR(0)); - return 350; + return; } TetrisAI_data* tetrisai_data = reinterpret_cast(SEGENV.data); @@ -170,6 +184,7 @@ uint16_t mode_2DTetrisAI() tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); tetrisai_data->tetris.state = TetrisAIGame::States::INIT; + tetrisai_data->clearingStartTime = 0; SEGMENT.fill(SEGCOLOR(1)); } @@ -184,7 +199,21 @@ uint16_t mode_2DTetrisAI() tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui; } - if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) + //end line clearing flashing effect if needed + if (tetrisai_data->tetris.grid.gridBW.hasClearingRows()) + { + if (tetrisai_data->clearingStartTime == 0) { + tetrisai_data->clearingStartTime = strip.now; + } + if (strip.now - tetrisai_data->clearingStartTime > 750) + { + tetrisai_data->tetris.grid.gridBW.clearedLinesReadyForRemoval = true; + tetrisai_data->tetris.grid.cleanupFullLines(); + tetrisai_data->clearingStartTime = 0; + } + drawGrid(&tetrisai_data->tetris, tetrisai_data); + } + else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) { if (strip.now - tetrisai_data->lastTime > msDelayMove) @@ -222,8 +251,6 @@ uint16_t mode_2DTetrisAI() { tetrisai_data->tetris.poll(); } - - return FRAMETIME; } // mode_2DTetrisAI() static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11"; @@ -231,6 +258,7 @@ class TetrisAIUsermod : public Usermod { private: + static const char _name[]; public: void setup() @@ -238,6 +266,20 @@ class TetrisAIUsermod : public Usermod strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI); } + void addToConfig(JsonObject& root) override + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top["noFlashOnClear"] = noFlashOnClear; + } + + bool readFromConfig(JsonObject& root) override + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + configComplete &= getJsonValue(top["noFlashOnClear"], noFlashOnClear); + return configComplete; + } + void loop() { @@ -249,6 +291,7 @@ class TetrisAIUsermod : public Usermod } }; +const char TetrisAIUsermod::_name[] PROGMEM = "TetrisAI_v2"; static TetrisAIUsermod tetrisai_v2; REGISTER_USERMOD(tetrisai_v2); \ No newline at end of file diff --git a/usermods/TetrisAI_v2/gridbw.h b/usermods/TetrisAI_v2/gridbw.h index deea027d79..5db6e25a9b 100644 --- a/usermods/TetrisAI_v2/gridbw.h +++ b/usermods/TetrisAI_v2/gridbw.h @@ -13,7 +13,6 @@ #ifndef __GRIDBW_H__ #define __GRIDBW_H__ -#include #include #include "pieces.h" @@ -26,11 +25,18 @@ class GridBW uint8_t width; uint8_t height; std::vector pixels; + // When a row fills, we mark it here first so it can flash before being + // fully removed. + std::vector clearingRows; + // True when a line clearing flashing effect is over and we're ready to + // fully clean up the lines + bool clearedLinesReadyForRemoval = false; GridBW(uint8_t width, uint8_t height): width(width), height(height), - pixels(height) + pixels(height), + clearingRows(height) { if (width > 32) { @@ -85,9 +91,26 @@ class GridBW piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0; } + bool hasClearingRows() + { + for (bool rowClearing : clearingRows) + { + if (rowClearing) + { + return true; + } + } + return false; + } + void cleanupFullLines() { + // Skip cleanup if there are rows clearing + if (hasClearingRows() && !clearedLinesReadyForRemoval) { + return; + } uint8_t offset = 0; + bool doneRemovingClearedLines = false; //from "height - 1" to "0", so from bottom row to top for (uint8_t row = height; row-- > 0; ) @@ -95,8 +118,13 @@ class GridBW //full line? if (isLineFull(row)) { - offset++; - pixels[row] = 0x0; + if (clearedLinesReadyForRemoval) { + offset++; + pixels[row] = 0x0; + doneRemovingClearedLines = true; + } else { + clearingRows[row] = true; + } continue; } @@ -106,11 +134,20 @@ class GridBW pixels[row] = 0x0; } } + if (doneRemovingClearedLines) { + clearingRows.assign(height, false); + clearedLinesReadyForRemoval = false; + } } bool isLineFull(uint8_t y) { - return pixels[y] == (uint32_t)((1 << width) - 1); + return pixels[y] == (width >= 32 ? UINT32_MAX : (1U << width) - 1); + } + + bool isLineReadyForRemoval(uint8_t y) + { + return clearedLinesReadyForRemoval && isLineFull(y); } void reset() @@ -122,6 +159,8 @@ class GridBW pixels.clear(); pixels.resize(height); + clearingRows.assign(height, false); + clearedLinesReadyForRemoval = false; } }; diff --git a/usermods/TetrisAI_v2/gridcolor.h b/usermods/TetrisAI_v2/gridcolor.h index 815c2a5603..5f69027968 100644 --- a/usermods/TetrisAI_v2/gridcolor.h +++ b/usermods/TetrisAI_v2/gridcolor.h @@ -82,7 +82,7 @@ class GridColor //from "height - 1" to "0", so from bottom row to top for (uint8_t y = height; y-- > 0; ) { - if (gridBW.isLineFull(y)) + if (gridBW.isLineReadyForRemoval(y)) { offset++; for (uint8_t x = 0; x < width; x++) diff --git a/usermods/TetrisAI_v2/pieces.h b/usermods/TetrisAI_v2/pieces.h index 5d461615ae..0a13704dcf 100644 --- a/usermods/TetrisAI_v2/pieces.h +++ b/usermods/TetrisAI_v2/pieces.h @@ -19,7 +19,6 @@ #include #include #include -#include #define numPieces 7 diff --git a/usermods/TetrisAI_v2/readme.md b/usermods/TetrisAI_v2/readme.md index 5ac8028967..7962a9010d 100644 --- a/usermods/TetrisAI_v2/readme.md +++ b/usermods/TetrisAI_v2/readme.md @@ -2,13 +2,16 @@ This usermod adds a self-playing Tetris game as an 'effect'. The mod requires version 0.14 or higher as it relies on matrix support. The effect was tested on an ESP32 4MB with a WS2812B 16x16 matrix. -Version 1.0 +PHOTOSENSITIVE EPILEPSY WARNING: By default the effect features a flashing animation on line clear. This can be disabled +from the usermod settings page in WLED. ## Installation -Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/wled-dev/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)). +To activate the usermod, add the following line to your platformio_override.ini +`custom_usermods = tetrisai_v2` +The effect will then become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/wled-dev/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)). -If needed simply add to `platformio_override.ini` (or `platformio_override.ini`): +If needed simply add to `platformio_override.ini`: ```ini board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv diff --git a/usermods/TetrisAI_v2/tetrisai.h b/usermods/TetrisAI_v2/tetrisai.h index ba4fe60e43..7bb1d9321a 100644 --- a/usermods/TetrisAI_v2/tetrisai.h +++ b/usermods/TetrisAI_v2/tetrisai.h @@ -68,7 +68,7 @@ class TetrisAI } //line full if all ones in mask :-) - if (grid.isLineFull(row)) + if (grid.isLineReadyForRemoval(row)) { rating->fullLines++; } diff --git a/usermods/TetrisAI_v2/tetrisbag.h b/usermods/TetrisAI_v2/tetrisbag.h index 592dac6c7f..b1698d8143 100644 --- a/usermods/TetrisAI_v2/tetrisbag.h +++ b/usermods/TetrisAI_v2/tetrisbag.h @@ -15,7 +15,6 @@ #include #include -#include #include "tetrisbag.h" @@ -87,17 +86,12 @@ class TetrisBag void queuePiece() { //move vector to left - std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end()); + for (uint8_t i = 1; i < piecesQueue.size(); i++) { + piecesQueue[i - 1] = piecesQueue[i]; + } piecesQueue[piecesQueue.size() - 1] = getNextPiece(); } - void queuePiece(uint8_t idx) - { - //move vector to left - std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end()); - piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces); - } - void reset() { bag.clear(); diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index d91e1bf2d3..f5200ef4d2 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1206,7 +1206,7 @@ class AudioReactive : public Usermod { break; #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) case 5: - DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); + DEBUGSR_PRINT(F("AR: Generic PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) delay(100); @@ -1624,7 +1624,8 @@ class AudioReactive : public Usermod { if (audioSource->getType() == AudioSource::Type_I2SAdc) { infoArr.add(F("ADC analog")); } else { - infoArr.add(F("I2S digital")); + if (dmType == 5) infoArr.add(F("PDM digital")); // dmType 5 => generic PDM microphone + else infoArr.add(F("I2S digital")); } // input level or "silence" if (maxSample5sec > 1.0f) { @@ -1742,14 +1743,14 @@ class AudioReactive : public Usermod { } #endif } - if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { + if (palettes > 0 && root.containsKey(F("rmcpal"))) { // handle removal of custom palettes from JSON call so we don't break things removeAudioPalettes(); } } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<10) { + if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 5, 0)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) @@ -225,15 +225,17 @@ class I2SSource : public AudioSource { _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm if clock pin not provided. PDM is not supported on ESP32-S2. PDM RX not supported on ESP32-C3 _config.channel_format =I2S_PDM_MIC_CHANNEL; // seems that PDM mono mode always uses left channel. - _config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality + _config.use_apll = false; // don't use aPLL clock source (fix for #5391) #endif } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) if (mclkPin != I2S_PIN_NO_CHANGE) { + #if !defined(WLED_USE_ETHERNET) // fix for #5391 aPLL resource conflict - aPLL is needed for ethernet boards with internal RMII clock _config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality, and to avoid glitches. // //_config.fixed_mclk = 512 * _sampleRate; // //_config.fixed_mclk = 256 * _sampleRate; + #endif } #if !defined(SOC_I2S_SUPPORTS_APLL) @@ -622,7 +624,7 @@ class I2SAdcSource : public I2SSource { } // see example in https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino - adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11); // configure ADC input amplification + adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_12); // configure ADC input amplification #if defined(I2S_GRAB_ADC1_COMPLETELY) // according to docs from espressif, the ADC needs to be started explicitly diff --git a/usermods/deep_sleep/deep_sleep.cpp b/usermods/deep_sleep/deep_sleep.cpp index 65cebc5edf..cff40f86de 100644 --- a/usermods/deep_sleep/deep_sleep.cpp +++ b/usermods/deep_sleep/deep_sleep.cpp @@ -156,7 +156,7 @@ class DeepSleepUsermod : public Usermod { delay(1000); // just in case: give user a short ~10s window to turn LEDs on in UI (delaycounter is 10 by default) return; } - if (powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (handleIO() does enable offMode temporarily in this case) + if (powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (beginStrip() / handleIO() does enable offMode temporarily in this case) delaycounter--; if (delaycounter == 1 && offMode) { // force turn on, no matter the settings (device is bricked if user set sleepDelay=0, no bootup preset and turnOnAtBoot=false) if (briS == 0) bri = 10; // turn on and set low brightness to avoid automatic turn off diff --git a/usermods/pixels_dice_tray/BLE_REQUIREMENT.md b/usermods/pixels_dice_tray/BLE_REQUIREMENT.md new file mode 100644 index 0000000000..3b3607715e --- /dev/null +++ b/usermods/pixels_dice_tray/BLE_REQUIREMENT.md @@ -0,0 +1,48 @@ +# pixels_dice_tray Usermod - BLE Requirement Notice + +## Important: This Usermod Requires Special Configuration + +The `pixels_dice_tray` usermod requires **ESP32 BLE (Bluetooth Low Energy)** support, which is not available in all WLED build configurations. + +### Why is library.json disabled? + +The `library.json` file has been renamed to `library.json.disabled` to prevent this usermod from being automatically included in builds that use `custom_usermods = *` (like the `usermods` environment in platformio.ini). + +The Tasmota Arduino ESP32 platform used by WLED does not include Arduino BLE library by default, which causes compilation failures when this usermod is auto-included. + +### How to Use This Usermod + +This usermod **requires a custom build configuration**. You cannot simply enable it with `custom_usermods = *`. + +1. **Copy the sample configuration:** + ```bash + cp platformio_override.ini.sample ../../../platformio_override.ini + ``` + +2. **Edit `platformio_override.ini`** to match your ESP32 board configuration + +3. **Build with the custom environment:** + ```bash + pio run -e t_qt_pro_8MB_dice + # or + pio run -e esp32s3dev_8MB_qspi_dice + ``` + +### Platform Requirements + +- ESP32-S3 or compatible ESP32 board with BLE support +- Custom platformio environment (see `platformio_override.ini.sample`) +- Cannot be used with ESP8266 or ESP32-S2 + +### Re-enabling for Custom Builds + +If you want to use this usermod in a custom build: + +1. Rename `library.json.disabled` back to `library.json` +2. Manually add it to your custom environment's `custom_usermods` list +3. Ensure your platform includes BLE support + +### References + +- See `README.md` for full usermod documentation +- See `platformio_override.ini.sample` for build configuration examples diff --git a/usermods/pixels_dice_tray/led_effects.h b/usermods/pixels_dice_tray/led_effects.h index 7553d6817f..542d86c29b 100644 --- a/usermods/pixels_dice_tray/led_effects.h +++ b/usermods/pixels_dice_tray/led_effects.h @@ -8,10 +8,10 @@ #include "dice_state.h" // Reuse FX display functions. -extern uint16_t mode_breath(); -extern uint16_t mode_blends(); -extern uint16_t mode_glitter(); -extern uint16_t mode_gravcenter(); +extern void mode_breath(); +extern void mode_blends(); +extern void mode_glitter(); +extern void mode_gravcenter(); static constexpr uint8_t USER_ANY_DIE = 0xFF; /** @@ -40,8 +40,8 @@ static pixels::RollEvent GetLastRollForSegment() { * Alternating pixels running function (copied static function). */ // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) -#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3) -static uint16_t running_copy(uint32_t color1, uint32_t color2, bool theatre = false) { +#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) +static void running_copy(uint32_t color1, uint32_t color2, bool theatre = false) { int width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window uint32_t cycleTime = 50 + (255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; @@ -63,10 +63,9 @@ static uint16_t running_copy(uint32_t color1, uint32_t color2, bool theatre = fa SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1)); SEGENV.step = it; } - return FRAMETIME; } -static uint16_t simple_roll() { +static void simple_roll() { auto roll = GetLastRollForSegment(); if (roll.state != pixels::RollState::ON_FACE) { SEGMENT.fill(0); @@ -79,7 +78,6 @@ static uint16_t simple_roll() { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } } - return FRAMETIME; } // See https://kno.wled.ge/interfaces/json-api/#effect-metadata // Name - DieSimple @@ -92,31 +90,34 @@ static uint16_t simple_roll() { static const char _data_FX_MODE_SIMPLE_DIE[] PROGMEM = "DieSimple@,,Selected Die;!,!;;1;c1=255"; -static uint16_t pulse_roll() { +static void pulse_roll() { auto roll = GetLastRollForSegment(); if (roll.state != pixels::RollState::ON_FACE) { - return mode_breath(); + mode_breath(); + return; } else { - uint16_t ret = mode_blends(); + mode_blends(); uint16_t num_segments = float(roll.current_face + 1) / 20.0 * SEGLEN; for (int i = num_segments; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } - return ret; } } static const char _data_FX_MODE_PULSE_DIE[] PROGMEM = "DiePulse@!,!,Selected Die;!,!;!;1;sx=24,pal=50,c1=255"; -static uint16_t check_roll() { +static void check_roll() { auto roll = GetLastRollForSegment(); if (roll.state != pixels::RollState::ON_FACE) { - return running_copy(SEGCOLOR(0), SEGCOLOR(2)); + running_copy(SEGCOLOR(0), SEGCOLOR(2)); + return; } else { if (roll.current_face + 1 >= SEGMENT.custom2) { - return mode_glitter(); + mode_glitter(); + return; } else { - return mode_gravcenter(); + mode_gravcenter(); + return; } } } diff --git a/usermods/pixels_dice_tray/library.json b/usermods/pixels_dice_tray/library.json deleted file mode 100644 index ac1a7a0786..0000000000 --- a/usermods/pixels_dice_tray/library.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "pixels_dice_tray", - "build": { "libArchive": false}, - "dependencies": { - "arduino-pixels-dice":"https://github.com/axlan/arduino-pixels-dice.git", - "BLE":"*" - } -} diff --git a/usermods/pov_display/pov_display.cpp b/usermods/pov_display/pov_display.cpp index ac68e1b209..c57f3d8d59 100644 --- a/usermods/pov_display/pov_display.cpp +++ b/usermods/pov_display/pov_display.cpp @@ -5,37 +5,37 @@ static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;"; static POV s_pov; -uint16_t mode_pov_image(void) { +void mode_pov_image(void) { Segment& mainseg = strip.getMainSegment(); const char* segName = mainseg.name; if (!segName) { - return FRAMETIME; + return; } // Only proceed for files ending with .bmp (case-insensitive) size_t segLen = strlen(segName); - if (segLen < 4) return FRAMETIME; + if (segLen < 4) return; const char* ext = segName + (segLen - 4); // compare case-insensitive to ".bmp" if (!((ext[0]=='.') && (ext[1]=='b' || ext[1]=='B') && (ext[2]=='m' || ext[2]=='M') && (ext[3]=='p' || ext[3]=='P'))) { - return FRAMETIME; + return; } const char* current = s_pov.getFilename(); if (current && strcmp(segName, current) == 0) { s_pov.showNextLine(); - return FRAMETIME; + return; } static unsigned long s_lastLoadAttemptMs = 0; unsigned long nowMs = millis(); // Retry at most twice per second if the image is not yet loaded. - if (nowMs - s_lastLoadAttemptMs < 500) return FRAMETIME; + if (nowMs - s_lastLoadAttemptMs < 500) return; s_lastLoadAttemptMs = nowMs; s_pov.loadImage(segName); - return FRAMETIME; + return; } class PovDisplayUsermod : public Usermod { diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp index 6be3a92640..3ec46f6f77 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp @@ -60,8 +60,8 @@ class RgbRotaryEncoderUsermod : public Usermod // …then set only the LED pin _pins[0] = static_cast(ledIo); BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0); - - ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1); + busCfg.iType = BusManager::getI(busCfg.type, busCfg.pins, busCfg.driverType); // assign internal bus type and output driver + ledBus = new BusDigital(busCfg); if (!ledBus->isOk()) { cleanup(); return; diff --git a/usermods/user_fx/README.md b/usermods/user_fx/README.md index 704b71df01..2d6e800e46 100644 --- a/usermods/user_fx/README.md +++ b/usermods/user_fx/README.md @@ -4,6 +4,7 @@ This usermod is a common place to put various users’ WLED effects. It lets you Multiple Effects can be specified inside this single usermod, as we will illustrate below. You will be able to define them with custom names, sliders, etc. as with any other Effect. +* [Installation](./README.md#installation) * [How The Usermod Works](./README.md#how-the-usermod-works) * [Basic Syntax for WLED Effect Creation](./README.md#basic-syntax-for-wled-effect-creation) * [Understanding 2D WLED Effects](./README.md#understanding-2d-wled-effects) @@ -14,6 +15,17 @@ Multiple Effects can be specified inside this single usermod, as we will illustr * [Change Log](./README.md#change-log) * [Contact Us](./README.md#contact-us) +## Installation + +To activate the usermod, add the following line to your platformio_override.ini +```ini +custom_usermods = user_fx +``` +Or if you are already using a usermod, append user_fx to the list +```ini +custom_usermods = audioreactive user_fx +``` + ## How The Usermod Works The `user_fx.cpp` file can be broken down into four main parts: @@ -76,12 +88,14 @@ The first line of the code imports the [wled.h](https://github.com/wled/WLED/blo ### Static Effect Definition The next code block is the `mode_static` definition. This is usually left as `SEGMENT.fill(SEGCOLOR(0));` to leave all pixels off if the effect fails to load, but in theory one could use this as a 'fallback effect' to take on a different behavior, such as displaying some other color instead of leaving the pixels off. +`FX_FALLBACK_STATIC` is a macro that calls `mode_static()` and then returns. + ### User Effect Definitions Pre-loaded in this template is an example 2D Effect called "Diffusion Fire". (This is the name that would be shown in the UI once the binary is compiled and run on your device, as defined in the metadata string.) -The effect starts off by checking to see if the segment that the effect is being applied to is a 2D Matrix, and if it is not, then it returns the static effect which displays no pattern: +The effect starts off by checking to see if the segment that the effect is being applied to is a 2D Matrix, and if it is not, then it runs the static effect which displays no pattern: ```cpp if (!strip.isMatrix || !SEGMENT.is2D()) -return mode_static(); // not a 2D set-up + FX_FALLBACK_STATIC; // not a 2D set-up ``` The next code block contains several constant variable definitions which essentially serve to extract the dimensions of the user's 2D matrix and allow WLED to interpret the matrix as a 1D coordinate system (WLED must do this for all 2D animations): ```cpp @@ -128,7 +142,7 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW ```cpp if (!SEGENV.allocateData(dataSize)) -return mode_static(); // allocation failed + FX_FALLBACK_STATIC; // allocation failed ``` * Upon the first call, this section allocates a persistent data buffer tied to the segment environment (`SEGENV.data`). All subsequent calls simply ensure that the data is still valid. * The syntax `SEGENV.allocateData(n)` requests a buffer of size n bytes (1 byte per pixel here). @@ -250,20 +264,14 @@ After calculating tmp_row, we now handle rendering the pixels by updating the ac * `SEGCOLOR(0)` gets the first user-selected color for the segment. * The final line of code fades that base color according to the heat value (acts as brightness multiplier). -The final piece of this custom effect returns the frame time: +* Even though the effect logic itself controls when to update based on refresh_ms, WLED will still call this function at roughly FRAMETIME intervals (the FPS limit set in config) to check whether an update is needed. If nothing needs to change, the frame still needs to be re-rendered so color or brightness transitions will be smooth. + +If you want to run your effect at a fixed frame rate you can use the following code to not update your effect state, be aware however that transitions for your effect will also run at this frame rate - for example if you limit your effect to say 5 FPS, brightness changes and color changes may not look smooth. Also `SEGMENT.call` is still incremented on each function call. ```cpp -} -return FRAMETIME; -} +//limit update rate +if (strip.now - SEGENV.step < FRAMETIME_FIXED) return; +SEGENV.step = strip.now; ``` -* The first bracket closes the earlier `if ((strip.now - SEGENV.step) >= refresh_ms)` block. - * It ensures that the fire simulation (scrolling, sparking, diffusion, rendering) only runs when enough time has passed since the last update. -* returning the frame time tells WLED how soon this effect wants to be called again. - * `FRAMETIME` is a predefined macro in WLED, typically set to ~16ms, corresponding to ~60 FPS (frames per second). - * Even though the effect logic itself controls when to update based on refresh_ms, WLED will still call this function at roughly FRAMETIME intervals to check whether an update is needed. -* ⚠️ Important: Because the actual frame logic is gated by strip.now - SEGENV.step, returning FRAMETIME here doesn’t cause excessive updates — it just keeps the engine responsive. **Also note that an Effect should ALWAYS return FRAMETIME. Not doing so can cause glitches.** -* The final bracket closes the `mode_diffusionfire()` function itself. - ### The Metadata String At the end of every effect is an important line of code called the **metadata string**. @@ -310,13 +318,13 @@ We will break this effect down step by step. (This effect was originally one of the FastLED example effects; more information on FastLED can be found [here](https://fastled.io/).) ```cpp -static uint16_t sinelon_base(bool dual, bool rainbow=false) { +static void sinelon_base(bool dual, bool rainbow=false) { ``` * The first line of code defines `sinelon base` as static helper function. This is how all effects are initially defined. * Notice that it has some optional flags; these parameters will allow us to easily define the effect in different ways in the UI. ```cpp - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; ``` * If segment length ≤ 1, there’s nothing to animate. Just show static mode. @@ -396,28 +404,21 @@ This final part of the effect function will fill in the 'trailing' pixels to com * Works in both directions: Forward (if new pos > old pos), and Backward (if new pos < old pos). * Updates `SEGENV.aux0` to current position at the end. -Finally, we return the `FRAMETIME`, as with all effect functions: -```cpp - return FRAMETIME; -} -``` -* Returns `FRAMETIME` constant to set effect update rate (usually ~16 ms). - The last part of this effect has the Wrapper functions for different Sinelon modes. Notice that there are three different modes that we can define from the single effect definition by leveraging the arguments in the function: ```cpp -uint16_t mode_sinelon(void) { - return sinelon_base(false); +void mode_sinelon(void) { + sinelon_base(false); } // Calls sinelon_base with dual = false and rainbow = false -uint16_t mode_sinelon_dual(void) { - return sinelon_base(true); +void mode_sinelon_dual(void) { + sinelon_base(true); } // Calls sinelon_base with dual = true and rainbow = false -uint16_t mode_sinelon_rainbow(void) { - return sinelon_base(false, true); +void mode_sinelon_rainbow(void) { + sinelon_base(false, true); } // Calls sinelon_base with dual = false and rainbow = true ``` diff --git a/usermods/user_fx/user_fx.cpp b/usermods/user_fx/user_fx.cpp index da6937c87d..2258b8ad4f 100644 --- a/usermods/user_fx/user_fx.cpp +++ b/usermods/user_fx/user_fx.cpp @@ -2,20 +2,30 @@ // for information how FX metadata strings work see https://kno.wled.ge/interfaces/json-api/#effect-metadata +// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) +#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) + +#define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) + // static effect, used if an effect fails to initialize -static uint16_t mode_static(void) { +static void mode_static(void) { SEGMENT.fill(SEGCOLOR(0)); - return strip.isOffRefreshRequired() ? FRAMETIME : 350; } +#define FX_FALLBACK_STATIC { mode_static(); return; } + +// If you define configuration options in your class and need to reference them in your effect function, add them here. +// If you only need to use them in your class you can define them as class members instead. +// bool myConfigValue = false; + ///////////////////////// // User FX functions // ///////////////////////// // Diffusion Fire: fire effect intended for 2D setups smaller than 16x16 -static uint16_t mode_diffusionfire(void) { +static void mode_diffusionfire(void) { if (!strip.isMatrix || !SEGMENT.is2D()) - return mode_static(); // not a 2D set-up + FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -29,7 +39,7 @@ static uint16_t mode_diffusionfire(void) { unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vWidth()*vHeight() for 2D if (!SEGENV.allocateData(dataSize)) - return mode_static(); // allocation failed + FX_FALLBACK_STATIC; // allocation failed if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -84,11 +94,1172 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW } } } - return FRAMETIME; } static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spark rate,Diffusion Speed,Turbulence,,Use palette;;Color;;2;pal=35"; +/* + * Spinning Wheel effect - LED animates around 1D strip (or each column in a 2D matrix), slows down and stops at random position + * Created by Bob Loeffler and claude.ai + * First slider (Spin speed) is for the speed of the moving/spinning LED (random number within a narrow speed range). + * If value is 0, a random speed will be selected from the full range of values. + * Second slider (Spin slowdown start time) is for how long before the slowdown phase starts (random number within a narrow time range). + * If value is 0, a random time will be selected from the full range of values. + * Third slider (Spinner size) is for the number of pixels that make up the spinner. + * Fourth slider (Spin delay) is for how long it takes for the LED to start spinning again after the previous spin. + * The first checkbox allows the spinner to spin. If it's enabled, the spinner will do its thing. If it's not enabled, it will wait for the user to enable + * it either by clicking the checkbox or by pressing a physical button (e.g. using a playlist to run a couple presets that have JSON API codes). + * The second checkbox sets "color per block" mode. Enabled means that each spinner block will be the same color no matter what its LED position is. + * The third checkbox enables synchronized restart (all spinners restart together instead of individually). + * aux0 stores the settings checksum to detect changes + * aux1 stores the color scale for performance + */ + +static void mode_spinning_wheel(void) { + if (SEGLEN < 1) FX_FALLBACK_STATIC; + + unsigned strips = SEGMENT.nrOfVStrips(); + if (strips == 0) FX_FALLBACK_STATIC; + + constexpr unsigned stateVarsPerStrip = 8; + unsigned dataSize = sizeof(uint32_t) * stateVarsPerStrip; + if (!SEGENV.allocateData(dataSize * strips)) FX_FALLBACK_STATIC; + uint32_t* state = reinterpret_cast(SEGENV.data); + // state[0] = current position (fixed point: upper 16 bits = position, lower 16 bits = fraction) + // state[1] = velocity (fixed point: pixels per frame * 65536) + // state[2] = phase (0=fast spin, 1=slowing, 2=wobble, 3=stopped) + // state[3] = stop time (when phase 3 was entered) + // state[4] = wobble step (0=at stop pos, 1=moved back, 2=returned to stop) + // state[5] = slowdown start time (when to transition from phase 0 to phase 1) + // state[6] = wobble timing (for 200ms / 400ms / 300ms delays) + // state[7] = store the stop position per strip + + // state[] index values for easier readability + constexpr unsigned CUR_POS_IDX = 0; // state[0] + constexpr unsigned VELOCITY_IDX = 1; + constexpr unsigned PHASE_IDX = 2; + constexpr unsigned STOP_TIME_IDX = 3; + constexpr unsigned WOBBLE_STEP_IDX = 4; + constexpr unsigned SLOWDOWN_TIME_IDX = 5; + constexpr unsigned WOBBLE_TIME_IDX = 6; + constexpr unsigned STOP_POS_IDX = 7; + + SEGMENT.fill(SEGCOLOR(1)); + + // Handle random seeding globally (outside the virtual strip) + if (SEGENV.call == 0) { + random16_set_seed(hw_random16()); + SEGENV.aux1 = (255 << 8) / SEGLEN; // Cache the color scaling + } + + // Check if settings changed (do this once, not per virtual strip) + uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom3 + SEGMENT.check1 + SEGMENT.check3; + bool settingsChanged = (SEGENV.aux0 != settingssum); + if (settingsChanged) { + random16_add_entropy(hw_random16()); + SEGENV.aux0 = settingssum; + } + + // Check if all spinners are stopped and ready to restart (for synchronized restart) + bool allReadyToRestart = true; + if (SEGMENT.check3) { + uint8_t spinnerSize = map(SEGMENT.custom1, 0, 255, 1, 10); + uint16_t spin_delay = map(SEGMENT.custom3, 0, 31, 2000, 15000); + uint32_t now = strip.now; + + for (unsigned stripNr = 0; stripNr < strips; stripNr += spinnerSize) { + uint32_t* stripState = &state[stripNr * stateVarsPerStrip]; + // Check if this spinner is stopped AND has waited its delay + if (stripState[PHASE_IDX] != 3 || stripState[STOP_TIME_IDX] == 0) { + allReadyToRestart = false; + break; + } + // Check if delay has elapsed + if ((now - stripState[STOP_TIME_IDX]) < spin_delay) { + allReadyToRestart = false; + break; + } + } + } + + struct virtualStrip { + static void runStrip(uint16_t stripNr, uint32_t* state, bool settingsChanged, bool allReadyToRestart, unsigned strips) { + uint8_t phase = state[PHASE_IDX]; + uint32_t now = strip.now; + + // Check for restart conditions + bool needsReset = false; + if (SEGENV.call == 0) { + needsReset = true; + } else if (settingsChanged && SEGMENT.check1) { + needsReset = true; + } else if (phase == 3 && state[STOP_TIME_IDX] != 0) { + // If synchronized restart is enabled, only restart when all strips are ready + if (SEGMENT.check3) { + if (allReadyToRestart) { + needsReset = true; + } + } else { + // Normal mode: restart after individual strip delay + uint16_t spin_delay = map(SEGMENT.custom3, 0, 31, 2000, 15000); + if ((now - state[STOP_TIME_IDX]) >= spin_delay) { + needsReset = true; + } + } + } + + // Initialize or restart + if (needsReset && SEGMENT.check1) { // spin the spinner(s) only if the "Spin me!" checkbox is enabled + state[CUR_POS_IDX] = 0; + + // Set velocity + uint16_t speed = map(SEGMENT.speed, 0, 255, 300, 800); + if (speed == 300) { // random speed (user selected 0 on speed slider) + state[VELOCITY_IDX] = random16(200, 900) * 655; // fixed-point velocity scaling (approx. 65536/100) + } else { + state[VELOCITY_IDX] = random16(speed - 100, speed + 100) * 655; + } + + // Set slowdown start time + uint16_t slowdown = map(SEGMENT.intensity, 0, 255, 3000, 5000); + if (slowdown == 3000) { // random slowdown start time (user selected 0 on intensity slider) + state[SLOWDOWN_TIME_IDX] = now + random16(2000, 6000); + } else { + state[SLOWDOWN_TIME_IDX] = now + random16(slowdown - 1000, slowdown + 1000); + } + + state[PHASE_IDX] = 0; + state[STOP_TIME_IDX] = 0; + state[WOBBLE_STEP_IDX] = 0; + state[WOBBLE_TIME_IDX] = 0; + state[STOP_POS_IDX] = 0; // Initialize stop position + phase = 0; + } + + uint32_t pos_fixed = state[CUR_POS_IDX]; + uint32_t velocity = state[VELOCITY_IDX]; + + // Phase management + if (phase == 0) { + // Fast spinning phase + if ((int32_t)(now - state[SLOWDOWN_TIME_IDX]) >= 0) { + phase = 1; + state[PHASE_IDX] = 1; + } + } else if (phase == 1) { + // Slowing phase - apply deceleration + uint32_t decel = velocity / 80; + if (decel < 100) decel = 100; + + velocity = (velocity > decel) ? velocity - decel : 0; + state[VELOCITY_IDX] = velocity; + + // Check if stopped + if (velocity < 2000) { + velocity = 0; + state[VELOCITY_IDX] = 0; + phase = 2; + state[PHASE_IDX] = 2; + state[WOBBLE_STEP_IDX] = 0; + uint16_t stop_pos = (pos_fixed >> 16) % SEGLEN; + state[STOP_POS_IDX] = stop_pos; + state[WOBBLE_TIME_IDX] = now; + } + } else if (phase == 2) { + // Wobble phase (moves the LED back one and then forward one) + uint32_t wobble_step = state[WOBBLE_STEP_IDX]; + uint16_t stop_pos = state[STOP_POS_IDX]; + uint32_t elapsed = now - state[WOBBLE_TIME_IDX]; + + if (wobble_step == 0 && elapsed >= 200) { + // Move back one LED from stop position + uint16_t back_pos = (stop_pos == 0) ? SEGLEN - 1 : stop_pos - 1; + pos_fixed = ((uint32_t)back_pos) << 16; + state[CUR_POS_IDX] = pos_fixed; + state[WOBBLE_STEP_IDX] = 1; + state[WOBBLE_TIME_IDX] = now; + } else if (wobble_step == 1 && elapsed >= 400) { + // Move forward to the stop position + pos_fixed = ((uint32_t)stop_pos) << 16; + state[CUR_POS_IDX] = pos_fixed; + state[WOBBLE_STEP_IDX] = 2; + state[WOBBLE_TIME_IDX] = now; + } else if (wobble_step == 2 && elapsed >= 300) { + // Wobble complete, enter stopped phase + phase = 3; + state[PHASE_IDX] = 3; + state[STOP_TIME_IDX] = now; + } + } + + // Update position (phases 0 and 1 only) + if (phase == 0 || phase == 1) { + pos_fixed += velocity; + state[CUR_POS_IDX] = pos_fixed; + } + + // Draw LED for all phases + uint16_t pos = (pos_fixed >> 16) % SEGLEN; + + uint8_t spinnerSize = map(SEGMENT.custom1, 0, 255, 1, 10); + + // Calculate color once per spinner block (based on strip number, not position) + uint8_t hue; + if (SEGMENT.check2) { + // Each spinner block gets its own color based on strip number + uint16_t numSpinners = max(1U, (strips + spinnerSize - 1) / spinnerSize); + hue = (uint32_t)(255) * (stripNr / spinnerSize) / numSpinners; + } else { + // Color changes with position + hue = (SEGENV.aux1 * pos) >> 8; + } + + uint32_t color = ColorFromPaletteWLED(SEGPALETTE, hue, 255, LINEARBLEND); + + // Draw the spinner with configurable size (1-10 LEDs) + for (int8_t x = 0; x < spinnerSize; x++) { + for (uint8_t y = 0; y < spinnerSize; y++) { + uint16_t drawPos = (pos + y) % SEGLEN; + int16_t drawStrip = stripNr + x; + + // Wrap horizontally if needed, or skip if out of bounds + if (drawStrip >= 0 && drawStrip < strips) { + SEGMENT.setPixelColor(indexToVStrip(drawPos, drawStrip), color); + } + } + } + } + }; + + for (unsigned stripNr = 0; stripNr < strips; stripNr++) { + // Only run on strips that are multiples of spinnerSize to avoid overlap + uint8_t spinnerSize = map(SEGMENT.custom1, 0, 255, 1, 10); + if (stripNr % spinnerSize == 0) { + virtualStrip::runStrip(stripNr, &state[stripNr * stateVarsPerStrip], settingsChanged, allReadyToRestart, strips); + } + } +} +static const char _data_FX_MODE_SPINNINGWHEEL[] PROGMEM = "Spinning Wheel@Speed (0=random),Slowdown (0=random),Spinner size,,Spin delay,Spin me!,Color per block,Sync restart;!,!;!;;m12=1,c1=1,c3=8,o1=1,o3=1"; + + +/* +/ Lava Lamp 2D effect +* Uses particles to simulate rising blobs of "lava" or wax +* Particles slowly rise, merge to create organic flowing shapes, and then fall to the bottom to start again +* Created by Bob Loeffler using claude.ai +* The first slider sets the number of active blobs +* The second slider sets the size range of the blobs +* The third slider sets the damping value for horizontal blob movement +* The Attract checkbox sets the attraction of blobs (checked will make the blobs attract other close blobs horizontally) +* The Keep Color Ratio checkbox sets whether we preserve the color ratio when displaying pixels that are in 2 or more overlapping blobs +* aux0 keeps track of the blob size value +* aux1 keeps track of the number of blobs +*/ + +typedef struct LavaParticle { + float x, y; // Position + float vx, vy; // Velocity + float size; // Blob size + uint8_t hue; // Color + bool active; // will not be displayed if false + uint16_t delayTop; // number of frames to wait at top before falling again + bool idleTop; // sitting idle at the top + uint16_t delayBottom; // number of frames to wait at bottom before rising again + bool idleBottom; // sitting idle at the bottom +} LavaParticle; + +static void mode_2D_lavalamp(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up + + const uint16_t cols = SEG_W; + const uint16_t rows = SEG_H; + constexpr float MAX_BLOB_RADIUS = 20.0f; // cap to prevent frame rate drops on large matrices + constexpr size_t MAX_LAVA_PARTICLES = 34; // increasing this value could cause slowness for large matrices + constexpr size_t MAX_TOP_FPS_DELAY = 900; // max delay when particles are at the top + constexpr size_t MAX_BOTTOM_FPS_DELAY = 1200; // max delay when particles are at the bottom + + // Allocate per-segment storage + if (!SEGENV.allocateData(sizeof(LavaParticle) * MAX_LAVA_PARTICLES)) FX_FALLBACK_STATIC; + LavaParticle* lavaParticles = reinterpret_cast(SEGENV.data); + + // Initialize particles on first call + if (SEGENV.call == 0) { + for (int i = 0; i < MAX_LAVA_PARTICLES; i++) { + lavaParticles[i].active = false; + } + } + + // Track particle size and particle count slider changes, re-initialize if either changes + uint8_t currentNumParticles = (SEGMENT.intensity >> 3) + 3; + uint8_t currentSize = SEGMENT.custom1; + if (currentNumParticles > MAX_LAVA_PARTICLES) currentNumParticles = MAX_LAVA_PARTICLES; + bool needsReinit = (currentSize != SEGENV.aux0) || (currentNumParticles != SEGENV.aux1); + + if (needsReinit) { + for (int i = 0; i < MAX_LAVA_PARTICLES; i++) { + lavaParticles[i].active = false; + } + SEGENV.aux0 = currentSize; + SEGENV.aux1 = currentNumParticles; + } + + uint8_t size = currentSize; + uint8_t numParticles = currentNumParticles; + + // blob size based on matrix width + const float minSize = cols * 0.15f; // Minimum 15% of width + const float maxSize = cols * 0.4f; // Maximum 40% of width + float sizeRange = (maxSize - minSize) * (size / 255.0f); + int rangeInt = max(1, (int)(sizeRange)); + + // calculate the spawning area for the particles + const float spawnXStart = cols * 0.20f; + const float spawnXWidth = cols * 0.60f; + int spawnX = max(1, (int)(spawnXWidth)); + + bool preserveColorRatio = SEGMENT.check3; + + // Spawn new particles at the bottom near the center + for (int i = 0; i < MAX_LAVA_PARTICLES; i++) { + if (!lavaParticles[i].active && hw_random8() < 32) { // spawn when slot available + // Spawn in the middle 60% of the matrix width + lavaParticles[i].x = spawnXStart + (float)hw_random16(spawnX); + lavaParticles[i].y = rows - 1; + lavaParticles[i].vx = (hw_random16(7) - 3) / 250.0f; + lavaParticles[i].vy = -(hw_random16(20) + 10) / 100.0f * 0.3f; + + lavaParticles[i].size = minSize + (float)hw_random16(rangeInt); + if (lavaParticles[i].size > MAX_BLOB_RADIUS) lavaParticles[i].size = MAX_BLOB_RADIUS; + + lavaParticles[i].hue = hw_random8(); + lavaParticles[i].active = true; + + // Set random delays when particles are at top and bottom + lavaParticles[i].delayTop = hw_random16(MAX_TOP_FPS_DELAY); + lavaParticles[i].delayBottom = hw_random16(MAX_BOTTOM_FPS_DELAY); + lavaParticles[i].idleBottom = true; + break; + } + } + + // Fade background slightly for trailing effect + SEGMENT.fadeToBlackBy(40); + + // Update and draw particles + int activeCount = 0; + unsigned long currentMillis = strip.now; + for (int i = 0; i < MAX_LAVA_PARTICLES; i++) { + if (!lavaParticles[i].active) continue; + activeCount++; + + // Keep particle count on target by deactivating excess particles + if (activeCount > numParticles) { + lavaParticles[i].active = false; + activeCount--; + continue; + } + + LavaParticle *p = &lavaParticles[i]; + + // Physics update + p->x += p->vx; + p->y += p->vy; + + // Optional particle/blob attraction + if (SEGMENT.check2) { + for (int j = 0; j < MAX_LAVA_PARTICLES; j++) { + if (i == j || !lavaParticles[j].active) continue; + + LavaParticle *other = &lavaParticles[j]; + + // Skip attraction if moving in same vertical direction (both up or both down) + if ((p->vy < 0 && other->vy < 0) || (p->vy > 0 && other->vy > 0)) continue; + + float dx = other->x - p->x; + float dy = other->y - p->y; + + // Apply weak horizontal attraction only + float attractRange = p->size + other->size; + float distSq = dx*dx + dy*dy; + float attractRangeSq = attractRange * attractRange; + if (distSq > 0 && distSq < attractRangeSq) { + float dist = sqrt(distSq); // Only compute sqrt when needed + float force = (1.0f - (dist / attractRange)) * 0.0001f; + p->vx += (dx / dist) * force; + } + } + } + + // Horizontal oscillation (makes it more organic) + float damping= map(SEGMENT.custom2, 0, 255, 97, 87) / 100.0f; + p->vx += sin((currentMillis / 1000.0f + i) * 0.5f) * 0.002f; // Reduced oscillation + p->vx *= damping; // damping for more or less horizontal drift + + // Bounce off sides (don't affect vertical velocity) + if (p->x < 0) { + p->x = 0; + p->vx = abs(p->vx); // reverse horizontal + } + if (p->x >= cols) { + p->x = cols - 1; + p->vx = -abs(p->vx); // reverse horizontal + } + + // Adjust rise/fall velocity depending on approx distance from heat source (at bottom) + // In top 1/4th of rows... + if (p->y < rows * .25f) { + if (p->vy >= 0) { // if going down, delay the particles so they won't go down immediately + if (p->delayTop > 0 && p->idleTop) { + p->vy = 0.0f; + p->delayTop--; + p->idleTop = true; + } else { + p->vy = 0.01f; + p->delayTop = hw_random16(MAX_TOP_FPS_DELAY); + p->idleTop = false; + } + } else if (p->vy <= 0) { // if going up, slow down the rise rate + p->vy = -0.03f; + } + } + + // In next 1/4th of rows... + if (p->y <= rows * .50f && p->y >= rows * .25f) { + if (p->vy > 0) { // if going down, speed up the fall rate + p->vy = 0.03f; + } else if (p->vy <= 0) { // if going up, speed up the rise rate a little more + p->vy = -0.05f; + } + } + + // In next 1/4th of rows... + if (p->y <= rows * .75f && p->y >= rows * .50f) { + if (p->vy > 0) { // if going down, speed up the fall rate a little more + p->vy = 0.04f; + } else if (p->vy <= 0) { // if going up, speed up the rise rate + p->vy = -0.03f; + } + } + + // In bottom 1/4th of rows... + if (p->y > rows * .75f) { + if (p->vy >= 0) { // if going down, slow down the fall rate + p->vy = 0.02f; + } else if (p->vy <= 0) { // if going up, delay the particles so they won't go up immediately + if (p->delayBottom > 0 && p->idleBottom) { + p->vy = 0.0f; + p->delayBottom--; + p->idleBottom = true; + } else { + p->vy = -0.01f; + p->delayBottom = hw_random16(MAX_BOTTOM_FPS_DELAY); + p->idleBottom = false; + } + } + } + + // Boundary handling with reversal of direction + // When reaching TOP (y=0 area), reverse to fall back down, but need to delay first + if (p->y <= 0.5f * p->size) { + p->y = 0.5f * p->size; + if (p->vy < 0) { + p->vy = 0.005f; // set to a tiny positive value to start falling very slowly + p->idleTop = true; + } + } + + // When reaching BOTTOM (y=rows-1 area), reverse to rise back up, but need to delay first + if (p->y >= rows - 0.5f * p->size) { + p->y = rows - 0.5f * p->size; + if (p->vy > 0) { + p->vy = -0.005f; // set to a tiny negative value to start rising very slowly + p->idleBottom = true; + } + } + + // Get color + uint32_t color; + color = SEGMENT.color_from_palette(p->hue, true, PALETTE_SOLID_WRAP, 0); + + // Extract RGB and apply life/opacity + uint8_t w = (W(color) * 255) >> 8; + uint8_t r = (R(color) * 255) >> 8; + uint8_t g = (G(color) * 255) >> 8; + uint8_t b = (B(color) * 255) >> 8; + + // Draw blob with sub-pixel accuracy using bilinear distribution + float sizeSq = p->size * p->size; + + // Get fractional offsets of particle center + float fracX = p->x - floorf(p->x); + float fracY = p->y - floorf(p->y); + int centerX = (int)floorf(p->x); + int centerY = (int)floorf(p->y); + + for (int dy = -(int)p->size - 1; dy <= (int)p->size + 1; dy++) { + for (int dx = -(int)p->size - 1; dx <= (int)p->size + 1; dx++) { + int px = centerX + dx; + int py = centerY + dy; + + if (px < 0 || px >= cols || py < 0 || py >= rows) continue; + + // Sub-pixel distance: measure from true float center to pixel center + float subDx = dx - fracX; // distance from true center to this pixel's center + float subDy = dy - fracY; + float distSq = subDx * subDx + subDy * subDy; + + if (distSq < sizeSq) { + float intensity = 1.0f - (distSq / sizeSq); + intensity = intensity * intensity; // smooth falloff + + uint8_t bw = (uint8_t)(w * intensity); + uint8_t br = (uint8_t)(r * intensity); + uint8_t bg = (uint8_t)(g * intensity); + uint8_t bb = (uint8_t)(b * intensity); + + uint32_t existing = SEGMENT.getPixelColorXY(px, py); + uint32_t newColor = RGBW32(br, bg, bb, bw); + SEGMENT.setPixelColorXY(px, py, color_add(existing, newColor, preserveColorRatio ? true : false)); + } + } + } + } +} +static const char _data_FX_MODE_2D_LAVALAMP[] PROGMEM = "Lava Lamp@,# of blobs,Blob size,H. Damping,,,Attract,Keep Color Ratio;;!;2;ix=64,c2=192,o2=1,o3=1,pal=47"; + + +/* +/ Magma effect +* 2D magma/lava animation +* Adapted from FireLamp_JeeUI implementation (https://github.com/DmytroKorniienko/FireLamp_JeeUI/tree/dev) +* Original idea by SottNick, remastered by kostyamat +* Adapted to WLED by Bob Loeffler and claude.ai +* First slider (speed) is for the speed or flow rate of the moving magma. +* Second slider (intensity) is for the height of the magma. +* Third slider (lava bombs) is for the number of lava bombs (particles). The max # is 1/2 the number of columns on the 2D matrix. +* Fourth slider (gravity) is for how high the lava bombs will go. +* The checkbox (check2) is for whether the lava bombs can be seen in the magma or behind it. +*/ + +// Draw the magma +static void drawMagma(const uint16_t width, const uint16_t height, float *ff_y, float *ff_z, uint8_t *shiftHue) { + // Noise parameters - adjust these for different magma characteristics + // deltaValue: higher = more detailed/turbulent magma + // deltaHue: higher = taller magma structures + constexpr uint8_t magmaDeltaValue = 12U; + constexpr uint8_t magmaDeltaHue = 10U; + + uint16_t ff_y_int = (uint16_t)*ff_y; + uint16_t ff_z_int = (uint16_t)*ff_z; + + for (uint16_t i = 0; i < width; i++) { + for (uint16_t j = 0; j < height; j++) { + // Generate Perlin noise value (0-255) + uint8_t noise = perlin8(i * magmaDeltaValue, (j + ff_y_int + hw_random8(2)) * magmaDeltaHue, ff_z_int); + uint8_t paletteIndex = qsub8(noise, shiftHue[j]); // Apply the vertical fade gradient + CRGB col = SEGMENT.color_from_palette(paletteIndex, false, PALETTE_SOLID_WRAP, 0); // Get color from palette + SEGMENT.addPixelColorXY(i, height - 1 - j, col); // magma rises from bottom of display + } + } +} + +// Move and draw lava bombs (particles) +static void drawLavaBombs(const uint16_t width, const uint16_t height, float *particleData, float gravity, uint8_t particleCount) { + for (uint16_t i = 0; i < particleCount; i++) { + uint16_t idx = i * 4; + + particleData[idx + 3] -= gravity; + particleData[idx + 0] += particleData[idx + 2]; + particleData[idx + 1] += particleData[idx + 3]; + + float posX = particleData[idx + 0]; + float posY = particleData[idx + 1]; + + if (posY > height + height / 4) { + particleData[idx + 3] = -particleData[idx + 3] * 0.8f; + } + + if (posY < (float)(height / 8) - 1.0f || posX < 0 || posX >= width) { + particleData[idx + 0] = hw_random(0, width * 100) / 100.0f; + particleData[idx + 1] = hw_random(0, height * 25) / 100.0f; + particleData[idx + 2] = hw_random(-75, 75) / 100.0f; + + float baseVelocity = hw_random(60, 120) / 100.0f; + if (hw_random8() < 50) { + baseVelocity *= 1.6f; + } + particleData[idx + 3] = baseVelocity; + continue; + } + + int16_t xi = (int16_t)posX; + int16_t yi = (int16_t)posY; + + if (xi >= 0 && xi < width && yi >= 0 && yi < height) { + // Get a random color from the current palette + uint8_t randomIndex = hw_random8(64, 128); + CRGB pcolor = ColorFromPaletteWLED(SEGPALETTE, randomIndex, 255, LINEARBLEND); + + // Pre-calculate anti-aliasing weights + float xf = posX - xi; + float yf = posY - yi; + float ix = 1.0f - xf; + float iy = 1.0f - yf; + + uint8_t w0 = 255 * ix * iy; + uint8_t w1 = 255 * xf * iy; + uint8_t w2 = 255 * ix * yf; + uint8_t w3 = 255 * xf * yf; + + int16_t yFlipped = height - 1 - yi; // Flip Y coordinate + + SEGMENT.addPixelColorXY(xi, yFlipped, pcolor.scale8(w0)); + if (xi + 1 < width) + SEGMENT.addPixelColorXY(xi + 1, yFlipped, pcolor.scale8(w1)); + if (yFlipped - 1 >= 0) + SEGMENT.addPixelColorXY(xi, yFlipped - 1, pcolor.scale8(w2)); + if (xi + 1 < width && yFlipped - 1 >= 0) + SEGMENT.addPixelColorXY(xi + 1, yFlipped - 1, pcolor.scale8(w3)); + } + } +} + +static void mode_2D_magma(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up + const uint16_t width = SEG_W; + const uint16_t height = SEG_H; + const uint8_t MAGMA_MAX_PARTICLES = width / 2; + if (MAGMA_MAX_PARTICLES < 2) FX_FALLBACK_STATIC; // matrix too narrow for lava bombs + constexpr size_t SETTINGS_SUM_BYTES = 4; // 4 bytes for settings sum + + // Allocate memory: particles (4 floats each) + 2 floats for noise counters + shiftHue cache + settingsSum + const uint16_t dataSize = (MAGMA_MAX_PARTICLES * 4 + 2) * sizeof(float) + height * sizeof(uint8_t) + SETTINGS_SUM_BYTES; + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; // allocation failed + + float* particleData = reinterpret_cast(SEGENV.data); + float* ff_y = &particleData[MAGMA_MAX_PARTICLES * 4]; + float* ff_z = &particleData[MAGMA_MAX_PARTICLES * 4 + 1]; + uint32_t* settingsSumPtr = reinterpret_cast(&particleData[MAGMA_MAX_PARTICLES * 4 + 2]); + uint8_t* shiftHue = reinterpret_cast(reinterpret_cast(settingsSumPtr) + SETTINGS_SUM_BYTES); + + // Check if settings changed + uint32_t settingsKey = (uint32_t)SEGMENT.speed | ((uint32_t)SEGMENT.intensity << 8) | + ((uint32_t)SEGMENT.custom1 << 16) | ((uint32_t)SEGMENT.custom2 << 24); + bool settingsChanged = (*settingsSumPtr != settingsKey); + + if (SEGENV.call == 0 || settingsChanged) { + // Intensity slider controls magma height + uint16_t intensity = SEGMENT.intensity; + uint16_t fadeRange = map(intensity, 0, 255, height / 3, height); + + // shiftHue controls the vertical color gradient (magma fades out toward top) + for (uint16_t j = 0; j < height; j++) { + if (j < fadeRange) { + // prevent division issues and ensure smooth gradient + if (fadeRange > 1) { + shiftHue[j] = (uint8_t)(j * 255 / (fadeRange - 1)); + } else { + shiftHue[j] = 0; // Single row magma = no fade + } + } else { + shiftHue[j] = 255; + } + } + + // Initialize all particles + for (uint16_t i = 0; i < MAGMA_MAX_PARTICLES; i++) { + uint16_t idx = i * 4; + particleData[idx + 0] = hw_random(0, width * 100) / 100.0f; + particleData[idx + 1] = hw_random(0, height * 25) / 100.0f; + particleData[idx + 2] = hw_random(-75, 75) / 100.0f; + + float baseVelocity = hw_random(60, 120) / 100.0f; + if (hw_random8() < 50) { + baseVelocity *= 1.6f; + } + particleData[idx + 3] = baseVelocity; + } + *ff_y = 0.0f; + *ff_z = 0.0f; + *settingsSumPtr = settingsKey; + } + + if (!shiftHue) FX_FALLBACK_STATIC; // safety check + + // Speed control + float speedfactor = SEGMENT.speed / 255.0f; + speedfactor = speedfactor * speedfactor * 1.5f; + if (speedfactor < 0.001f) speedfactor = 0.001f; + + // Gravity control + float gravity = map(SEGMENT.custom2, 0, 255, 5, 20) / 100.0f; + + // Number of particles (lava bombs) + uint8_t particleCount = map(SEGMENT.custom1, 0, 255, 0, MAGMA_MAX_PARTICLES); + particleCount = constrain(particleCount, 0, MAGMA_MAX_PARTICLES); + + // Draw lava bombs in front of magma (or behind it) + if (SEGMENT.check2) { + drawMagma(width, height, ff_y, ff_z, shiftHue); + SEGMENT.fadeToBlackBy(70); // Dim the entire display to create trailing effect + if (particleCount > 0) drawLavaBombs(width, height, particleData, gravity, particleCount); + } + else { + if (particleCount > 0) drawLavaBombs(width, height, particleData, gravity, particleCount); + SEGMENT.fadeToBlackBy(70); // Dim the entire display to create trailing effect + drawMagma(width, height, ff_y, ff_z, shiftHue); + } + + // noise counters based on speed slider + *ff_y += speedfactor * 2.0f; + *ff_z += speedfactor; + + SEGENV.step++; +} +static const char _data_FX_MODE_2D_MAGMA[] PROGMEM = "Magma@Flow rate,Magma height,Lava bombs,Gravity,,,Bombs in front;;!;2;ix=192,c2=32,o2=1,pal=35"; + + +/* +/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler 2025 +* First slider is for the ants' speed. +* Second slider is for the # of ants. +* Third slider is for the Ants' size. +* Fourth slider (custom2) is for blurring the LEDs in the segment. +* Checkbox1 is for Gathering food (enabled if you want the ants to gather food, disabled if they are just walking). +* We will switch directions when they get to the beginning or end of the segment when gathering food. +* When gathering food, the Pass By option will automatically be enabled so they can drop off their food easier (and look for more food). +* Checkbox2 is for Smear mode (enabled is smear pixel colors, disabled is no smearing) +* Checkbox3 is for whether the ants will bump into each other (disabled) or just pass by each other (enabled) +*/ + +// Ant structure representing each ant's state +struct Ant { + unsigned long lastBumpUpdate; // the last time the ant bumped into another ant + bool hasFood; + float velocity; + float position; // (0.0 to 1.0 range) +}; + +constexpr unsigned MAX_ANTS = 32; +constexpr float MIN_COLLISION_TIME_MS = 2.0f; +constexpr float VELOCITY_MIN = 2.0f; +constexpr float VELOCITY_MAX = 10.0f; +constexpr unsigned ANT_SIZE_MIN = 1; +constexpr unsigned ANT_SIZE_MAX = 20; + +// Helper function to get food pixel color based on ant and background colors +static uint32_t getFoodColor(uint32_t antColor, uint32_t backgroundColor) { + if (antColor == WHITE) + return (backgroundColor == YELLOW) ? GRAY : YELLOW; + return (backgroundColor == WHITE) ? YELLOW : WHITE; +} + +// Helper function to handle ant boundary wrapping or bouncing +static void handleBoundary(Ant& ant, float& position, bool gatherFood, bool atStart, unsigned long currentTime) { + if (gatherFood) { + // Bounce mode: reverse direction and update food status + position = atStart ? 0.0f : 1.0f; + ant.velocity = -ant.velocity; + ant.lastBumpUpdate = currentTime; + ant.position = position; + ant.hasFood = atStart; // Has food when leaving start, drops it at end + } else { + // Wrap mode: teleport to opposite end + position = atStart ? 1.0f : 0.0f; + ant.lastBumpUpdate = currentTime; + ant.position = position; + } +} + +// Helper function to calculate ant color +static uint32_t getAntColor(int antIndex, int numAnts, bool usePalette) { + if (usePalette) + return SEGMENT.color_from_palette(antIndex * 255 / numAnts, false, (paletteBlend == 1 || paletteBlend == 3), 255); + // Alternate between two colors for default palette + return (antIndex % 3 == 1) ? SEGCOLOR(0) : SEGCOLOR(2); +} + +// Helper function to render a single ant pixel with food handling +static void renderAntPixel(int pixelIndex, int pixelOffset, int antSize, const Ant& ant, uint32_t antColor, uint32_t backgroundColor, bool gatherFood) { + bool isMovingBackward = (ant.velocity < 0); + bool isFoodPixel = gatherFood && ant.hasFood && ((isMovingBackward && pixelOffset == 0) || (!isMovingBackward && pixelOffset == antSize - 1)); + if (isFoodPixel) { + SEGMENT.setPixelColor(pixelIndex, getFoodColor(antColor, backgroundColor)); + } else { + SEGMENT.setPixelColor(pixelIndex, antColor); + } +} + +static void mode_ants(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; + + // Allocate memory for ant data + uint32_t backgroundColor = SEGCOLOR(1); + unsigned dataSize = sizeof(Ant) * MAX_ANTS; + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; // Allocation failed + + Ant* ants = reinterpret_cast(SEGENV.data); + + // Extract configuration from segment settings + unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); + bool gatherFood = SEGMENT.check1; + bool SmearMode = SEGMENT.check2; + bool passBy = SEGMENT.check3 || gatherFood; // global no‑collision when gathering food is enabled + unsigned antSize = map(SEGMENT.custom1, 0, 255, ANT_SIZE_MIN, ANT_SIZE_MAX) + (gatherFood ? 1 : 0); + + // Initialize ants on first call + if (SEGENV.call == 0) { + int confusedAntIndex = hw_random(0, numAnts); // the first random ant to go backwards + + for (int i = 0; i < MAX_ANTS; i++) { + ants[i].lastBumpUpdate = strip.now; + + // Random velocity + float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f; + // One random ant moves in opposite direction + ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity; + // Random starting position (0.0 to 1.0) + ants[i].position = hw_random16(0, 10000) / 10000.0f; + // Ants don't have food yet + ants[i].hasFood = false; + } + } + + // Calculate time conversion factor based on speed slider + float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; + + // Clear background if not in Smear mode + if (!SmearMode) SEGMENT.fill(backgroundColor); + + // Update and render each ant + for (int i = 0; i < numAnts; i++) { + float timeSinceLastUpdate = float(int(strip.now - ants[i].lastBumpUpdate)) / timeConversionFactor; + float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate; + + // Reset ants that wandered too far off-track (e.g., after intensity change) + if (newPosition < -0.5f || newPosition > 1.5f) { + newPosition = ants[i].position = hw_random16(0, 10000) / 10000.0f; + ants[i].lastBumpUpdate = strip.now; + } + + // Handle boundary conditions (bounce or wrap) + if (newPosition <= 0.0f && ants[i].velocity < 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, true, strip.now); + } else if (newPosition >= 1.0f && ants[i].velocity > 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, false, strip.now); + } + + // Handle collisions between ants (if not passing by) + if (!passBy) { + for (int j = i + 1; j < numAnts; j++) { + if (fabsf(ants[j].velocity - ants[i].velocity) < 0.001f) continue; // Moving in same direction at same speed; avoids tiny denominators + + // Calculate collision time using physics - collisionTime formula adapted from rolling_balls + float timeOffset = float(int(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)); + float collisionTime = (timeConversionFactor * (ants[i].position - ants[j].position) + ants[i].velocity * timeOffset) / (ants[j].velocity - ants[i].velocity); + + // Check if collision occurred in valid time window + float timeSinceJ = float(int(strip.now - ants[j].lastBumpUpdate)); + if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) { + // Update positions to collision point + float adjustedTime = (collisionTime + float(int(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate))) / timeConversionFactor; + ants[i].position += ants[i].velocity * adjustedTime; + ants[j].position = ants[i].position; + + // Update collision time + unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; + ants[i].lastBumpUpdate = collisionMoment; + ants[j].lastBumpUpdate = collisionMoment; + + // Reverse the ant with greater speed magnitude + if (fabsf(ants[i].velocity) > fabsf(ants[j].velocity)) { + ants[i].velocity = -ants[i].velocity; + } else { + ants[j].velocity = -ants[j].velocity; + } + + // Recalculate position after collision + newPosition = ants[i].position + ants[i].velocity * float(int(strip.now - ants[i].lastBumpUpdate)) / timeConversionFactor; + } + } + } + + // Clamp position to valid range + newPosition = constrain(newPosition, 0.0f, 1.0f); + unsigned pixelPosition = roundf(newPosition * (SEGLEN - 1)); + + // Determine ant color + uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); + + // Render ant pixels + for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { + unsigned currentPixel = pixelPosition + pixelOffset; + if (currentPixel >= SEGLEN) break; + renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); + } + + // Update ant state + ants[i].lastBumpUpdate = strip.now; + ants[i].position = newPosition; + } + + SEGMENT.blur(SEGMENT.custom2>>1); +} +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Blur,,Gathering food,Smear,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,c2=0,o1=1,o3=1"; + + +/* +/ Morse Code by Bob Loeffler +* Adapted from code by automaticaddison.com and then optimized by claude.ai +* aux0 is the pattern offset for scrolling +* aux1 saves settings: check2 (1 bit), check3 (1 bit), text hash (4 bits) and pattern length (10 bits) +* The first slider (sx) selects the scrolling speed +* The second slider selects the color mode (lower half selects color wheel, upper half selects color palettes) +* Checkbox1 displays all letters in a word with the same color +* Checkbox2 displays punctuation or not +* Checkbox3 displays the End-of-message code or not +* We get the text from the SEGMENT.name and convert it to morse code +* This effect uses a bit array, instead of bool array, for efficient storage - 8x memory reduction (128 bytes vs 1024 bytes) +* +* Morse Code rules: +* - a dot is 1 pixel/LED; a dash is 3 pixels/LEDs +* - there is 1 space between each dot or dash that make up a letter/number/punctuation +* - there are 3 spaces between each letter/number/punctuation +* - there are 7 spaces between each word +*/ + +// Bit manipulation macros +#define SET_BIT8(arr, i) ((arr)[(i) >> 3] |= (1 << ((i) & 7))) +#define GET_BIT8(arr, i) (((arr)[(i) >> 3] & (1 << ((i) & 7))) != 0) + +// Build morse code pattern into a buffer +static void build_morsecode_pattern(const char *morse_code, uint8_t *pattern, uint8_t *wordIndex, uint16_t &index, uint8_t currentWord, int maxSize) { + const char *c = morse_code; + + // Build the dots and dashes into pattern array + while (*c != '\0') { + // it's a dot which is 1 pixel + if (*c == '.') { + if (index >= maxSize - 1) return; + SET_BIT8(pattern, index); + wordIndex[index] = currentWord; + index++; + } + else { // Must be a dash which is 3 pixels + if (index >= maxSize - 3) return; + SET_BIT8(pattern, index); + wordIndex[index] = currentWord; + index++; + SET_BIT8(pattern, index); + wordIndex[index] = currentWord; + index++; + SET_BIT8(pattern, index); + wordIndex[index] = currentWord; + index++; + } + + c++; + + // 1 space between parts of a letter/number/punctuation (but not after the last one) + if (*c != '\0') { + if (index >= maxSize) return; + wordIndex[index] = currentWord; + index++; + } + } + + // 3 spaces between two letters/numbers/punctuation + if (index >= maxSize - 2) return; + wordIndex[index] = currentWord; + index++; + if (index >= maxSize - 1) return; + wordIndex[index] = currentWord; + index++; + if (index >= maxSize) return; + wordIndex[index] = currentWord; + index++; +} + +static void mode_morsecode(void) { + if (SEGLEN < 1) FX_FALLBACK_STATIC; + + // A-Z in Morse Code + static const char * letters[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", + "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."}; + // 0-9 in Morse Code + static const char * numbers[] = {"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----."}; + + // Punctuation in Morse Code + struct PunctuationMapping { + char character; + const char* code; + }; + + static const PunctuationMapping punctuation[] = { + {'.', ".-.-.-"}, {',', "--..--"}, {'?', "..--.."}, + {':', "---..."}, {'-', "-....-"}, {'!', "-.-.--"}, + {'&', ".-..."}, {'@', ".--.-."}, {')', "-.--.-"}, + {'(', "-.--."}, {'/', "-..-."}, {'\'', ".----."} + }; + + // Get the text to display + char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; + size_t len = 0; + + if (SEGMENT.name) len = strlen(SEGMENT.name); + if (len == 0) { + strcpy_P(text, PSTR("I Love WLED!")); + } else { + strcpy(text, SEGMENT.name); + } + + // Convert to uppercase in place + for (char *p = text; *p; p++) { + *p = toupper(*p); + } + + // Allocate per-segment storage for pattern (1023 bits = 127 bytes) + word index array (1024 bytes) + word count (1 byte) + constexpr size_t MORSECODE_MAX_PATTERN_SIZE = 1023; + constexpr size_t MORSECODE_PATTERN_BYTES = (MORSECODE_MAX_PATTERN_SIZE + 7) / 8; // 128 bytes + constexpr size_t MORSECODE_WORD_INDEX_BYTES = MORSECODE_MAX_PATTERN_SIZE; // 1 byte per bit position + constexpr size_t MORSECODE_WORD_COUNT_BYTES = 1; // 1 byte for word count + if (!SEGENV.allocateData(MORSECODE_PATTERN_BYTES + MORSECODE_WORD_INDEX_BYTES + MORSECODE_WORD_COUNT_BYTES)) FX_FALLBACK_STATIC; + uint8_t* morsecodePattern = reinterpret_cast(SEGENV.data); + uint8_t* wordIndexArray = reinterpret_cast(SEGENV.data + MORSECODE_PATTERN_BYTES); + uint8_t* wordCountPtr = reinterpret_cast(SEGENV.data + MORSECODE_PATTERN_BYTES + MORSECODE_WORD_INDEX_BYTES); + + // SEGENV.aux1 stores: [bit 15: check2] [bit 14: check3] [bits 10-13: text hash (4 bits)] [bits 0-9: pattern length] + bool lastCheck2 = (SEGENV.aux1 & 0x8000) != 0; + bool lastCheck3 = (SEGENV.aux1 & 0x4000) != 0; + uint16_t lastHashBits = (SEGENV.aux1 >> 10) & 0xF; // 4 bits of hash + uint16_t patternLength = SEGENV.aux1 & 0x3FF; // Lower 10 bits for length (up to 1023) + + // Compute text hash + uint16_t textHash = 0; + for (char *p = text; *p; p++) { + textHash = ((textHash << 5) + textHash) + *p; + } + uint16_t currentHashBits = (textHash >> 12) & 0xF; // Use upper 4 bits of hash + + bool textChanged = (currentHashBits != lastHashBits) && (SEGENV.call > 0); + + // Check if we need to rebuild the pattern + bool needsRebuild = (SEGENV.call == 0) || textChanged || (SEGMENT.check2 != lastCheck2) || (SEGMENT.check3 != lastCheck3); + + // Initialize on first call or rebuild pattern + if (needsRebuild) { + patternLength = 0; + + // Clear the bit array and word index array first + memset(morsecodePattern, 0, MORSECODE_PATTERN_BYTES); + memset(wordIndexArray, 0, MORSECODE_WORD_INDEX_BYTES); + + // Track current word index + uint8_t currentWordIndex = 0; + + // Build complete morse code pattern + for (char *c = text; *c; c++) { + if (patternLength >= MORSECODE_MAX_PATTERN_SIZE - 10) break; + + if (*c >= 'A' && *c <= 'Z') { + build_morsecode_pattern(letters[*c - 'A'], morsecodePattern, wordIndexArray, patternLength, currentWordIndex, MORSECODE_MAX_PATTERN_SIZE); + } + else if (*c >= '0' && *c <= '9') { + build_morsecode_pattern(numbers[*c - '0'], morsecodePattern, wordIndexArray, patternLength, currentWordIndex, MORSECODE_MAX_PATTERN_SIZE); + } + else if (*c == ' ') { + // Space between words - increment word index for next word + currentWordIndex++; + // Add 4 additional spaces (7 total with the 3 after each letter) + for (int x = 0; x < 4; x++) { + if (patternLength >= MORSECODE_MAX_PATTERN_SIZE) break; + wordIndexArray[patternLength] = currentWordIndex; + patternLength++; + } + } + else if (SEGMENT.check2) { + const char *punctuationCode = nullptr; + for (const auto& p : punctuation) { + if (*c == p.character) { + punctuationCode = p.code; + break; + } + } + if (punctuationCode) { + build_morsecode_pattern(punctuationCode, morsecodePattern, wordIndexArray, patternLength, currentWordIndex, MORSECODE_MAX_PATTERN_SIZE); + } + } + } + + if (SEGMENT.check3) { + build_morsecode_pattern(".-.-.", morsecodePattern, wordIndexArray, patternLength, currentWordIndex, MORSECODE_MAX_PATTERN_SIZE); + } + + for (int x = 0; x < 7; x++) { + if (patternLength >= MORSECODE_MAX_PATTERN_SIZE) break; + wordIndexArray[patternLength] = currentWordIndex; + patternLength++; + } + + // Store the total number of words (currentWordIndex + 1 because it's 0-indexed) + *wordCountPtr = currentWordIndex + 1; + + // Store pattern length, checkbox states, and hash bits in aux1 + SEGENV.aux1 = patternLength | (currentHashBits << 10) | (SEGMENT.check2 ? 0x8000 : 0) | (SEGMENT.check3 ? 0x4000 : 0); + + // Reset the scroll offset + SEGENV.aux0 = 0; + } + + // if pattern is empty for some reason, display black background only + if (patternLength == 0) { + SEGMENT.fill(BLACK); + return; + } + + // Update offset to make the morse code scroll + // Use step for scroll timing only + uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*3; + uint32_t it = strip.now / cycleTime; + if (SEGENV.step != it) { + SEGENV.aux0++; + SEGENV.step = it; + } + + // Clear background + SEGMENT.fill(BLACK); + + // Draw the scrolling pattern + int offset = SEGENV.aux0 % patternLength; + + // Get the word count and calculate color spacing + uint8_t wordCount = *wordCountPtr; + if (wordCount == 0) wordCount = 1; + uint8_t colorSpacing = 255 / wordCount; // Distribute colors evenly across color wheel/palette + + for (int i = 0; i < SEGLEN; i++) { + int patternIndex = (offset + i) % patternLength; + if (GET_BIT8(morsecodePattern, patternIndex)) { + uint8_t wordIdx = wordIndexArray[patternIndex]; + if (SEGMENT.check1) { // make each word a separate color + if (SEGMENT.custom3 < 16) + // use word index to select base color, add slight offset for animation + SEGMENT.setPixelColor(i, SEGMENT.color_wheel((wordIdx * colorSpacing) + (SEGENV.aux0 / 4))); + else + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(wordIdx * colorSpacing, true, PALETTE_SOLID_WRAP, 0)); + } + else { + if (SEGMENT.custom3 < 16) + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.aux0 + i)); + else + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } + } +} +static const char _data_FX_MODE_MORSECODE[] PROGMEM = "Morse Code@Speed,,,,Color mode,Color by Word,Punctuation,EndOfMessage;;!;1;sx=192,c3=8,o1=1,o2=1"; + + ///////////////////// // UserMod Class // ///////////////////// @@ -98,6 +1269,11 @@ class UserFxUsermod : public Usermod { public: void setup() override { strip.addEffect(255, &mode_diffusionfire, _data_FX_MODE_DIFFUSIONFIRE); + strip.addEffect(255, &mode_spinning_wheel, _data_FX_MODE_SPINNINGWHEEL); + strip.addEffect(255, &mode_2D_lavalamp, _data_FX_MODE_2D_LAVALAMP); + strip.addEffect(255, &mode_2D_magma, _data_FX_MODE_2D_MAGMA); + strip.addEffect(255, &mode_ants, _data_FX_MODE_ANTS); + strip.addEffect(255, &mode_morsecode, _data_FX_MODE_MORSECODE); //////////////////////////////////////// // add your effect function(s) here // @@ -109,6 +1285,25 @@ class UserFxUsermod : public Usermod { // strip.addEffect(255, &mode_your_effect2, _data_FX_MODE_YOUR_EFFECT2); // strip.addEffect(255, &mode_your_effect3, _data_FX_MODE_YOUR_EFFECT3); } + + + /////////////////////////////////////////////////////////////////////////////////////////////// + // If you want configuration options in the usermod settings page, implement these methods // + /////////////////////////////////////////////////////////////////////////////////////////////// + + // void addToConfig(JsonObject& root) override + // { + // JsonObject top = root.createNestedObject(FPSTR("User FX")); + // top["myConfigValue"] = myConfigValue; + // } + // bool readFromConfig(JsonObject& root) override + // { + // JsonObject top = root[FPSTR("User FX")]; + // bool configComplete = !top.isNull(); + // configComplete &= getJsonValue(top["myConfigValue"], myConfigValue); + // return configComplete; + // } + void loop() override {} // nothing to do in the loop uint16_t getId() override { return USERMOD_ID_USER_FX; } }; diff --git a/usermods/usermod_v2_RF433/usermod_v2_RF433.cpp b/usermods/usermod_v2_RF433/usermod_v2_RF433.cpp index 9ac6c416d1..ed3d5eb26f 100644 --- a/usermods/usermod_v2_RF433/usermod_v2_RF433.cpp +++ b/usermods/usermod_v2_RF433/usermod_v2_RF433.cpp @@ -124,7 +124,7 @@ class RF433Usermod : public Usermod char objKey[14]; bool parsed = false; - if (!requestJSONBufferLock(22)) return false; + if (!requestJSONBufferLock(JSON_LOCK_REMOTE)) return false; sprintf_P(objKey, PSTR("\"%d\":"), button); diff --git a/usermods/usermod_v2_animartrix/usermod_v2_animartrix.cpp b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.cpp index d2968f2fbd..a90828d233 100644 --- a/usermods/usermod_v2_animartrix/usermod_v2_animartrix.cpp +++ b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.cpp @@ -85,266 +85,214 @@ class ANIMartRIXMod:public ANIMartRIX { }; ANIMartRIXMod anim; -uint16_t mode_Module_Experiment10() { +void mode_Module_Experiment10() { anim.initEffect(); anim.Module_Experiment10(); - return FRAMETIME; } -uint16_t mode_Module_Experiment9() { +void mode_Module_Experiment9() { anim.initEffect(); anim.Module_Experiment9(); - return FRAMETIME; } -uint16_t mode_Module_Experiment8() { +void mode_Module_Experiment8() { anim.initEffect(); anim.Module_Experiment8(); - return FRAMETIME; } -uint16_t mode_Module_Experiment7() { +void mode_Module_Experiment7() { anim.initEffect(); anim.Module_Experiment7(); - return FRAMETIME; } -uint16_t mode_Module_Experiment6() { +void mode_Module_Experiment6() { anim.initEffect(); anim.Module_Experiment6(); - return FRAMETIME; } -uint16_t mode_Module_Experiment5() { +void mode_Module_Experiment5() { anim.initEffect(); anim.Module_Experiment5(); - return FRAMETIME; } -uint16_t mode_Module_Experiment4() { +void mode_Module_Experiment4() { anim.initEffect(); anim.Module_Experiment4(); - return FRAMETIME; } -uint16_t mode_Zoom2() { +void mode_Zoom2() { anim.initEffect(); anim.Zoom2(); - return FRAMETIME; } -uint16_t mode_Module_Experiment3() { +void mode_Module_Experiment3() { anim.initEffect(); anim.Module_Experiment3(); - return FRAMETIME; } -uint16_t mode_Module_Experiment2() { +void mode_Module_Experiment2() { anim.initEffect(); anim.Module_Experiment2(); - return FRAMETIME; } -uint16_t mode_Module_Experiment1() { +void mode_Module_Experiment1() { anim.initEffect(); anim.Module_Experiment1(); - return FRAMETIME; } -uint16_t mode_Parametric_Water() { +void mode_Parametric_Water() { anim.initEffect(); anim.Parametric_Water(); - return FRAMETIME; } -uint16_t mode_Water() { +void mode_Water() { anim.initEffect(); anim.Water(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido_6() { +void mode_Complex_Kaleido_6() { anim.initEffect(); anim.Complex_Kaleido_6(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido_5() { +void mode_Complex_Kaleido_5() { anim.initEffect(); anim.Complex_Kaleido_5(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido_4() { +void mode_Complex_Kaleido_4() { anim.initEffect(); anim.Complex_Kaleido_4(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido_3() { +void mode_Complex_Kaleido_3() { anim.initEffect(); anim.Complex_Kaleido_3(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido_2() { +void mode_Complex_Kaleido_2() { anim.initEffect(); anim.Complex_Kaleido_2(); - return FRAMETIME; } -uint16_t mode_Complex_Kaleido() { +void mode_Complex_Kaleido() { anim.initEffect(); anim.Complex_Kaleido(); - return FRAMETIME; } -uint16_t mode_SM10() { +void mode_SM10() { anim.initEffect(); anim.SM10(); - return FRAMETIME; } -uint16_t mode_SM9() { +void mode_SM9() { anim.initEffect(); anim.SM9(); - return FRAMETIME; } -uint16_t mode_SM8() { +void mode_SM8() { anim.initEffect(); anim.SM8(); - return FRAMETIME; } -// uint16_t mode_SM7() { +// void mode_SM7() { // anim.initEffect(); // anim.SM7(); // -// return FRAMETIME; // } -uint16_t mode_SM6() { +void mode_SM6() { anim.initEffect(); anim.SM6(); - return FRAMETIME; } -uint16_t mode_SM5() { +void mode_SM5() { anim.initEffect(); anim.SM5(); - return FRAMETIME; } -uint16_t mode_SM4() { +void mode_SM4() { anim.initEffect(); anim.SM4(); - return FRAMETIME; } -uint16_t mode_SM3() { +void mode_SM3() { anim.initEffect(); anim.SM3(); - return FRAMETIME; } -uint16_t mode_SM2() { +void mode_SM2() { anim.initEffect(); anim.SM2(); - return FRAMETIME; } -uint16_t mode_SM1() { +void mode_SM1() { anim.initEffect(); anim.SM1(); - return FRAMETIME; } -uint16_t mode_Big_Caleido() { +void mode_Big_Caleido() { anim.initEffect(); anim.Big_Caleido(); - return FRAMETIME; } -uint16_t mode_RGB_Blobs5() { +void mode_RGB_Blobs5() { anim.initEffect(); anim.RGB_Blobs5(); - return FRAMETIME; } -uint16_t mode_RGB_Blobs4() { +void mode_RGB_Blobs4() { anim.initEffect(); anim.RGB_Blobs4(); - return FRAMETIME; } -uint16_t mode_RGB_Blobs3() { +void mode_RGB_Blobs3() { anim.initEffect(); anim.RGB_Blobs3(); - return FRAMETIME; } -uint16_t mode_RGB_Blobs2() { +void mode_RGB_Blobs2() { anim.initEffect(); anim.RGB_Blobs2(); - return FRAMETIME; } -uint16_t mode_RGB_Blobs() { +void mode_RGB_Blobs() { anim.initEffect(); anim.RGB_Blobs(); - return FRAMETIME; } -uint16_t mode_Polar_Waves() { +void mode_Polar_Waves() { anim.initEffect(); anim.Polar_Waves(); - return FRAMETIME; } -uint16_t mode_Slow_Fade() { +void mode_Slow_Fade() { anim.initEffect(); anim.Slow_Fade(); - return FRAMETIME; } -uint16_t mode_Zoom() { +void mode_Zoom() { anim.initEffect(); anim.Zoom(); - return FRAMETIME; } -uint16_t mode_Hot_Blob() { +void mode_Hot_Blob() { anim.initEffect(); anim.Hot_Blob(); - return FRAMETIME; } -uint16_t mode_Spiralus2() { +void mode_Spiralus2() { anim.initEffect(); anim.Spiralus2(); - return FRAMETIME; } -uint16_t mode_Spiralus() { +void mode_Spiralus() { anim.initEffect(); anim.Spiralus(); - return FRAMETIME; } -uint16_t mode_Yves() { +void mode_Yves() { anim.initEffect(); anim.Yves(); - return FRAMETIME; } -uint16_t mode_Scaledemo1() { +void mode_Scaledemo1() { anim.initEffect(); anim.Scaledemo1(); - return FRAMETIME; } -uint16_t mode_Lava1() { +void mode_Lava1() { anim.initEffect(); anim.Lava1(); - return FRAMETIME; } -uint16_t mode_Caleido3() { +void mode_Caleido3() { anim.initEffect(); anim.Caleido3(); - return FRAMETIME; } -uint16_t mode_Caleido2() { +void mode_Caleido2() { anim.initEffect(); anim.Caleido2(); - return FRAMETIME; } -uint16_t mode_Caleido1() { +void mode_Caleido1() { anim.initEffect(); anim.Caleido1(); - return FRAMETIME; } -uint16_t mode_Distance_Experiment() { +void mode_Distance_Experiment() { anim.initEffect(); anim.Distance_Experiment(); - return FRAMETIME; } -uint16_t mode_Center_Field() { +void mode_Center_Field() { anim.initEffect(); anim.Center_Field(); - return FRAMETIME; } -uint16_t mode_Waves() { +void mode_Waves() { anim.initEffect(); anim.Waves(); - return FRAMETIME; } -uint16_t mode_Chasing_Spirals() { +void mode_Chasing_Spirals() { anim.initEffect(); anim.Chasing_Spirals(); - return FRAMETIME; } -uint16_t mode_Rotating_Blob() { +void mode_Rotating_Blob() { anim.initEffect(); anim.Rotating_Blob(); - return FRAMETIME; } diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp index 1b97ea94da..fe508b1f32 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.cpp @@ -16,6 +16,10 @@ // It can be configured to load auto saved preset at startup, // during the first `loop()`. // +// By default it will not save the state if an unmodified preset +// is selected (to not duplicate it). You can change this behaviour +// by setting autoSaveIgnorePresets=false +// // AutoSaveUsermod is standalone, but if FourLineDisplayUsermod // is installed, it will notify the user of the saved changes. @@ -49,6 +53,8 @@ class AutoSaveUsermod : public Usermod { bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? #endif + bool autoSaveIgnorePresets = true; // ignore by default to not duplicate presets + // If we've detected the need to auto save, this will be non zero. unsigned long autoSaveAfter = 0; @@ -68,6 +74,7 @@ class AutoSaveUsermod : public Usermod { static const char _autoSaveAfterSec[]; static const char _autoSavePreset[]; static const char _autoSaveApplyOnBoot[]; + static const char _autoSaveIgnorePresets[]; void inline saveSettings() { char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; @@ -122,7 +129,8 @@ class AutoSaveUsermod : public Usermod { void loop() { static unsigned long lastRun = 0; unsigned long now = millis(); - if (!autoSaveAfterSec || !enabled || currentPreset>0 || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave + if (!autoSaveAfterSec || !enabled || (autoSaveIgnorePresets && currentPreset>0) || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave + lastRun = now; uint8_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; @@ -219,10 +227,11 @@ class AutoSaveUsermod : public Usermod { void addToConfig(JsonObject& root) { // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_autoSaveEnabled)] = enabled; - top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam - top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam - top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; + top[FPSTR(_autoSaveEnabled)] = enabled; + top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam + top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam + top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; + top[FPSTR(_autoSaveIgnorePresets)] = autoSaveIgnorePresets; DEBUG_PRINTLN(F("Autosave config saved.")); } @@ -245,12 +254,13 @@ class AutoSaveUsermod : public Usermod { return false; } - enabled = top[FPSTR(_autoSaveEnabled)] | enabled; - autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)] | autoSaveAfterSec; - autoSaveAfterSec = (uint16_t) min(3600,max(10,(int)autoSaveAfterSec)); // bounds checking - autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset; - autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking - applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot; + enabled = top[FPSTR(_autoSaveEnabled)] | enabled; + autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)] | autoSaveAfterSec; + autoSaveAfterSec = (uint16_t) min(3600,max(10,(int)autoSaveAfterSec)); // bounds checking + autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset; + autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking + applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot; + autoSaveIgnorePresets = top[FPSTR(_autoSaveIgnorePresets)] | autoSaveIgnorePresets; DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINTLN(F(" config (re)loaded.")); @@ -268,11 +278,12 @@ class AutoSaveUsermod : public Usermod { }; // strings to reduce flash memory usage (used more than twice) -const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; -const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled"; -const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; -const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; -const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; +const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; +const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled"; +const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; +const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; +const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; +const char AutoSaveUsermod::_autoSaveIgnorePresets[] PROGMEM = "autoSaveIgnorePresets"; static AutoSaveUsermod autosave; REGISTER_USERMOD(autosave); diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d60c525261..0b5ecdda94 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -14,6 +14,8 @@ #include "FX.h" #include "fcn_declare.h" +#define FX_FALLBACK_STATIC { mode_static(); return; } + #if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) #include "FXparticleSystem.h" // include particle system code only if at least one system is enabled #ifdef WLED_DISABLE_PARTICLESYSTEM2D @@ -63,8 +65,8 @@ #define IBN 5100 // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) -#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3) -#define PALETTE_MOVING_WRAP !(strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)) +#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) +#define PALETTE_MOVING_WRAP !(paletteBlend == 2 || (paletteBlend == 0 && SEGMENT.speed == 0)) #define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) @@ -80,12 +82,12 @@ //#define MAX_FREQ_LOG10 3.71f // effect utility functions -uint8_t sin_gap(uint16_t in) { +static uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0 } -uint16_t triwave16(uint16_t in) { +static uint16_t triwave16(uint16_t in) { if (in < 0x8000) return in *2; return 0xFFFF - (in - 0x8000)*2; } @@ -97,7 +99,7 @@ uint16_t triwave16(uint16_t in) { * @param attdec attack & decay, max. pulsewidth / 2 * @returns signed waveform value */ -int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { +static int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { int8_t a = 127; if (x > 127) { a = -127; @@ -131,20 +133,18 @@ static um_data_t* getAudioData() { /* * No blinking. Just plain old static light. */ -uint16_t mode_static(void) { +void mode_static(void) { SEGMENT.fill(SEGCOLOR(0)); - return strip.isOffRefreshRequired() ? FRAMETIME : 350; } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; /* * Copy a segment and perform (optional) color adjustments */ -uint16_t mode_copy_segment(void) { +void mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; if (sourceid >= strip.getSegmentsNum() || sourceid == strip.getCurrSegmentId()) { // invalid source SEGMENT.fadeToBlackBy(5); // fade out - return FRAMETIME; } Segment& sourcesegment = strip.getSegment(sourceid); @@ -184,7 +184,6 @@ uint16_t mode_copy_segment(void) { } } } - return FRAMETIME; } static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,Axis(2D),FullStack(last frame);;;12;ix=0,c1=0,c2=0,c3=0"; @@ -194,7 +193,7 @@ static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Ligh * Alternate between color1 and color2 * if(strobe == true) then create a strobe effect */ -uint16_t blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { +void blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { uint32_t cycleTime = (255 - SEGMENT.speed)*20; uint32_t onTime = FRAMETIME; if (!strobe) onTime += ((cycleTime * SEGMENT.intensity) >> 8); @@ -217,16 +216,14 @@ uint16_t blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } else SEGMENT.fill(color); - - return FRAMETIME; } /* * Normal blinking. Intensity sets duty cycle. */ -uint16_t mode_blink(void) { - return blink(SEGCOLOR(0), SEGCOLOR(1), false, true); +void mode_blink(void) { + blink(SEGCOLOR(0), SEGCOLOR(1), false, true); } static const char _data_FX_MODE_BLINK[] PROGMEM = "Blink@!,Duty cycle;!,!;!;01"; @@ -234,8 +231,8 @@ static const char _data_FX_MODE_BLINK[] PROGMEM = "Blink@!,Duty cycle;!,!;!;01"; /* * Classic Blink effect. Cycling through the rainbow. */ -uint16_t mode_blink_rainbow(void) { - return blink(SEGMENT.color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), false, false); +void mode_blink_rainbow(void) { + blink(SEGMENT.color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), false, false); } static const char _data_FX_MODE_BLINK_RAINBOW[] PROGMEM = "Blink Rainbow@Frequency,Blink duration;!,!;!;01"; @@ -243,7 +240,7 @@ static const char _data_FX_MODE_BLINK_RAINBOW[] PROGMEM = "Blink Rainbow@Frequen /* * Classic Strobe effect. */ -uint16_t mode_strobe(void) { +void mode_strobe(void) { return blink(SEGCOLOR(0), SEGCOLOR(1), true, true); } static const char _data_FX_MODE_STROBE[] PROGMEM = "Strobe@!;!,!;!;01"; @@ -252,7 +249,7 @@ static const char _data_FX_MODE_STROBE[] PROGMEM = "Strobe@!;!,!;!;01"; /* * Classic Strobe effect. Cycling through the rainbow. */ -uint16_t mode_strobe_rainbow(void) { +void mode_strobe_rainbow(void) { return blink(SEGMENT.color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), true, false); } static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!;!;01"; @@ -263,8 +260,8 @@ static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!; * LEDs are turned on (color1) in sequence, then turned off (color2) in sequence. * if (bool rev == true) then LEDs are turned off in reverse order */ -uint16_t color_wipe(bool rev, bool useRandomColors) { - if (SEGLEN <= 1) return mode_static(); +void color_wipe(bool rev, bool useRandomColors) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; unsigned prog = (perc * 65535) / cycleTime; @@ -311,15 +308,14 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { if (i == ledIndex) SEGMENT.setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, uint8_t(rem))); } } - return FRAMETIME; } /* * Lights all LEDs one after another. */ -uint16_t mode_color_wipe(void) { - return color_wipe(false, false); +void mode_color_wipe(void) { + color_wipe(false, false); } static const char _data_FX_MODE_COLOR_WIPE[] PROGMEM = "Wipe@!,!;!,!;!"; @@ -327,8 +323,8 @@ static const char _data_FX_MODE_COLOR_WIPE[] PROGMEM = "Wipe@!,!;!,!;!"; /* * Lights all LEDs one after another. Turns off opposite */ -uint16_t mode_color_sweep(void) { - return color_wipe(true, false); +void mode_color_sweep(void) { + color_wipe(true, false); } static const char _data_FX_MODE_COLOR_SWEEP[] PROGMEM = "Sweep@!,!;!,!;!"; @@ -337,8 +333,8 @@ static const char _data_FX_MODE_COLOR_SWEEP[] PROGMEM = "Sweep@!,!;!,!;!"; * Turns all LEDs after each other to a random color. * Then starts over with another color. */ -uint16_t mode_color_wipe_random(void) { - return color_wipe(false, true); +void mode_color_wipe_random(void) { + color_wipe(false, true); } static const char _data_FX_MODE_COLOR_WIPE_RANDOM[] PROGMEM = "Wipe Random@!;;!"; @@ -346,8 +342,8 @@ static const char _data_FX_MODE_COLOR_WIPE_RANDOM[] PROGMEM = "Wipe Random@!;;!" /* * Random color introduced alternating from start and end of strip. */ -uint16_t mode_color_sweep_random(void) { - return color_wipe(true, true); +void mode_color_sweep_random(void) { + color_wipe(true, true); } static const char _data_FX_MODE_COLOR_SWEEP_RANDOM[] PROGMEM = "Sweep Random@!;;!"; @@ -356,7 +352,7 @@ static const char _data_FX_MODE_COLOR_SWEEP_RANDOM[] PROGMEM = "Sweep Random@!;; * Lights all LEDs up in one random color. Then switches them * to the next random color. */ -uint16_t mode_random_color(void) { +void mode_random_color(void) { uint32_t cycleTime = 200 + (255 - SEGMENT.speed)*50; uint32_t it = strip.now / cycleTime; uint32_t rem = strip.now % cycleTime; @@ -380,7 +376,6 @@ uint16_t mode_random_color(void) { } SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux1), SEGMENT.color_wheel(SEGENV.aux0), uint8_t(fade))); - return FRAMETIME; } static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade time;;!;01"; @@ -389,8 +384,8 @@ static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade t * Lights every LED in a random color. Changes all LED at the same time * to new random colors. */ -uint16_t mode_dynamic(void) { - if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed +void mode_dynamic(void) { + if (!SEGENV.allocateData(SEGLEN)) FX_FALLBACK_STATIC; //allocation failed if(SEGENV.call == 0) { //SEGMENT.fill(BLACK); @@ -416,7 +411,6 @@ uint16_t mode_dynamic(void) { SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i])); } } - return FRAMETIME; } static const char _data_FX_MODE_DYNAMIC[] PROGMEM = "Dynamic@!,!,,,,Smooth;;!"; @@ -424,12 +418,11 @@ static const char _data_FX_MODE_DYNAMIC[] PROGMEM = "Dynamic@!,!,,,,Smooth;;!"; /* * effect "Dynamic" with smooth color-fading */ -uint16_t mode_dynamic_smooth(void) { +void mode_dynamic_smooth(void) { bool old = SEGMENT.check1; SEGMENT.check1 = true; mode_dynamic(); SEGMENT.check1 = old; - return FRAMETIME; } static const char _data_FX_MODE_DYNAMIC_SMOOTH[] PROGMEM = "Dynamic Smooth@!,!;;!"; @@ -437,7 +430,7 @@ static const char _data_FX_MODE_DYNAMIC_SMOOTH[] PROGMEM = "Dynamic Smooth@!,!;; /* * Does the "standby-breathing" of well known i-Devices. */ -uint16_t mode_breath(void) { +void mode_breath(void) { unsigned var = 0; unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)) & 0xFFFFU; counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 @@ -451,7 +444,6 @@ uint16_t mode_breath(void) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } - return FRAMETIME; } static const char _data_FX_MODE_BREATH[] PROGMEM = "Breathe@!;!,!;!;01"; @@ -459,15 +451,13 @@ static const char _data_FX_MODE_BREATH[] PROGMEM = "Breathe@!;!,!;!;01"; /* * Fades the LEDs between two colors */ -uint16_t mode_fade(void) { +void mode_fade(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)); uint8_t lum = triwave16(counter) >> 8; for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } - - return FRAMETIME; } static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01"; @@ -475,8 +465,8 @@ static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01"; /* * Scan mode parent function */ -uint16_t scan(bool dual) { - if (SEGLEN <= 1) return mode_static(); +void scan(bool dual) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; int prog = (perc * 65535) / cycleTime; @@ -498,16 +488,14 @@ uint16_t scan(bool dual) { for (int j = led_offset; j < led_offset + size; j++) { SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } - - return FRAMETIME; } /* * Runs a single pixel back and forth. */ -uint16_t mode_scan(void) { - return scan(false); +void mode_scan(void) { + scan(false); } static const char _data_FX_MODE_SCAN[] PROGMEM = "Scan@!,# of dots,,,,,Overlay;!,!,!;!"; @@ -515,8 +503,8 @@ static const char _data_FX_MODE_SCAN[] PROGMEM = "Scan@!,# of dots,,,,,Overlay;! /* * Runs two pixel back and forth in opposite directions. */ -uint16_t mode_dual_scan(void) { - return scan(true); +void mode_dual_scan(void) { + scan(true); } static const char _data_FX_MODE_DUAL_SCAN[] PROGMEM = "Scan Dual@!,# of dots,,,,,Overlay;!,!,!;!"; @@ -524,7 +512,7 @@ static const char _data_FX_MODE_DUAL_SCAN[] PROGMEM = "Scan Dual@!,# of dots,,,, /* * Cycles all LEDs at once through a rainbow. */ -uint16_t mode_rainbow(void) { +void mode_rainbow(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; @@ -533,8 +521,6 @@ uint16_t mode_rainbow(void) { } else { SEGMENT.fill(SEGMENT.color_wheel(counter)); } - - return FRAMETIME; } static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;;!;01"; @@ -542,7 +528,7 @@ static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;;!;0 /* * Cycles a rainbow over the entire string of LEDs. */ -uint16_t mode_rainbow_cycle(void) { +void mode_rainbow_cycle(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; @@ -551,8 +537,6 @@ uint16_t mode_rainbow_cycle(void) { uint8_t index = (i * (16 << (SEGMENT.intensity /29)) / SEGLEN) + counter; SEGMENT.setPixelColor(i, SEGMENT.color_wheel(index)); } - - return FRAMETIME; } static const char _data_FX_MODE_RAINBOW_CYCLE[] PROGMEM = "Rainbow@!,Size;;!"; @@ -560,7 +544,7 @@ static const char _data_FX_MODE_RAINBOW_CYCLE[] PROGMEM = "Rainbow@!,Size;;!"; /* * Alternating pixels running function. */ -static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { +static void running(uint32_t color1, uint32_t color2, bool theatre = false) { int width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window uint32_t cycleTime = 50 + (255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; @@ -582,7 +566,6 @@ static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1)); SEGENV.step = it; } - return FRAMETIME; } @@ -590,8 +573,8 @@ static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) * Theatre-style crawling lights. * Inspired by the Adafruit examples. */ -uint16_t mode_theater_chase(void) { - return running(SEGCOLOR(0), SEGCOLOR(1), true); +void mode_theater_chase(void) { + running(SEGCOLOR(0), SEGCOLOR(1), true); } static const char _data_FX_MODE_THEATER_CHASE[] PROGMEM = "Theater@!,Gap size;!,!;!"; @@ -600,8 +583,8 @@ static const char _data_FX_MODE_THEATER_CHASE[] PROGMEM = "Theater@!,Gap size;!, * Theatre-style crawling lights with rainbow effect. * Inspired by the Adafruit examples. */ -uint16_t mode_theater_chase_rainbow(void) { - return running(SEGMENT.color_wheel(SEGENV.step), SEGCOLOR(1), true); +void mode_theater_chase_rainbow(void) { + running(SEGMENT.color_wheel(SEGENV.step), SEGCOLOR(1), true); } static const char _data_FX_MODE_THEATER_CHASE_RAINBOW[] PROGMEM = "Theater Rainbow@!,Gap size;,!;!"; @@ -609,7 +592,7 @@ static const char _data_FX_MODE_THEATER_CHASE_RAINBOW[] PROGMEM = "Theater Rainb /* * Running lights effect with smooth sine transition base. */ -static uint16_t running_base(bool saw, bool dual=false) { +static void running_base(bool saw, bool dual=false) { unsigned x_scale = SEGMENT.intensity >> 2; uint32_t counter = (strip.now * SEGMENT.speed) >> 9; @@ -635,8 +618,6 @@ static uint16_t running_base(bool saw, bool dual=false) { } SEGMENT.setPixelColor(i, ca); } - - return FRAMETIME; } @@ -644,8 +625,8 @@ static uint16_t running_base(bool saw, bool dual=false) { * Running lights in opposite directions. * Idea: Make the gap width controllable with a third slider in the future */ -uint16_t mode_running_dual(void) { - return running_base(false, true); +void mode_running_dual(void) { + running_base(false, true); } static const char _data_FX_MODE_RUNNING_DUAL[] PROGMEM = "Running Dual@!,Wave width;L,!,R;!"; @@ -653,8 +634,8 @@ static const char _data_FX_MODE_RUNNING_DUAL[] PROGMEM = "Running Dual@!,Wave wi /* * Running lights effect with smooth sine transition. */ -uint16_t mode_running_lights(void) { - return running_base(false); +void mode_running_lights(void) { + running_base(false); } static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width;!,!;!"; @@ -662,8 +643,8 @@ static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width /* * Running lights effect with sawtooth transition. */ -uint16_t mode_saw(void) { - return running_base(true); +void mode_saw(void) { + running_base(true); } static const char _data_FX_MODE_SAW[] PROGMEM = "Saw@!,Width;!,!;!"; @@ -672,7 +653,7 @@ static const char _data_FX_MODE_SAW[] PROGMEM = "Saw@!,Width;!,!;!"; * Blink several LEDs in random colors on, reset, repeat. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t mode_twinkle(void) { +void mode_twinkle(void) { SEGMENT.fade_out(224); uint32_t cycleTime = 20 + (255 - SEGMENT.speed)*5; @@ -698,8 +679,6 @@ uint16_t mode_twinkle(void) { unsigned j = p >> 16; SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } - - return FRAMETIME; } static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; //pixels @@ -707,9 +686,9 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; /* * Dissolve function */ -uint16_t dissolve(uint32_t color) { +void dissolve(uint32_t color) { unsigned dataSize = sizeof(uint32_t) * SEGLEN; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { @@ -757,16 +736,14 @@ uint16_t dissolve(uint32_t color) { } else SEGENV.step++; } - - return FRAMETIME; } /* * Blink several LEDs on and then off */ -uint16_t mode_dissolve(void) { - return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(hw_random8()) : SEGCOLOR(0)); +void mode_dissolve(void) { + dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(hw_random8()) : SEGCOLOR(0)); } static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed,,,,Random,Complete;!,!;!"; @@ -774,8 +751,8 @@ static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Diss /* * Blink several LEDs on and then off in random colors */ -uint16_t mode_dissolve_random(void) { - return dissolve(SEGMENT.color_wheel(hw_random8())); +void mode_dissolve_random(void) { + dissolve(SEGMENT.color_wheel(hw_random8())); } static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!;!"; @@ -783,7 +760,7 @@ static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat * Blinks one LED at a time. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t mode_sparkle(void) { +void mode_sparkle(void) { if (!SEGMENT.check2) for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } @@ -796,7 +773,6 @@ uint16_t mode_sparkle(void) { } SEGMENT.setPixelColor(SEGENV.aux0, SEGCOLOR(0)); - return FRAMETIME; } static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!;!;;m12=0"; @@ -804,7 +780,7 @@ static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!; * Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark) * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t mode_flash_sparkle(void) { +void mode_flash_sparkle(void) { if (!SEGMENT.check2) for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } @@ -816,7 +792,6 @@ uint16_t mode_flash_sparkle(void) { SEGENV.step = strip.now; SEGENV.aux0 = 255-SEGMENT.speed; } - return FRAMETIME; } static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,,Overlay;Bg,Fx;!;;m12=0"; @@ -825,7 +800,7 @@ static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,, * Like flash sparkle. With more flash. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t mode_hyper_sparkle(void) { +void mode_hyper_sparkle(void) { if (!SEGMENT.check2) for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } @@ -840,7 +815,6 @@ uint16_t mode_hyper_sparkle(void) { SEGENV.step = strip.now; SEGENV.aux0 = 255-SEGMENT.speed; } - return FRAMETIME; } static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!,,,,,Overlay;Bg,Fx;!;;m12=0"; @@ -848,7 +822,7 @@ static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!,,,,,Over /* * Strobe effect with different strobe count and pause, controlled by speed. */ -uint16_t mode_multi_strobe(void) { +void mode_multi_strobe(void) { for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } @@ -869,8 +843,6 @@ uint16_t mode_multi_strobe(void) { if (SEGENV.aux1 > count) SEGENV.aux1 = 0; SEGENV.step = strip.now; } - - return FRAMETIME; } static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!;01"; @@ -878,8 +850,8 @@ static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!; /* * Android loading circle, refactored by @dedehai */ -uint16_t mode_android(void) { - if (!SEGENV.allocateData(sizeof(uint32_t))) return mode_static(); +void mode_android(void) { + if (!SEGENV.allocateData(sizeof(uint32_t))) FX_FALLBACK_STATIC; uint32_t* counter = reinterpret_cast(SEGENV.data); unsigned size = SEGENV.aux1 >> 1; // upper 15 bit unsigned shrinking = SEGENV.aux1 & 0x01; // lowest bit @@ -911,7 +883,6 @@ uint16_t mode_android(void) { else SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - return FRAMETIME; } static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!;!;;m12=1"; //vertical @@ -920,7 +891,7 @@ static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!;!;;m12= * color1 = background color * color2 and color3 = colors of two adjacent leds */ -static uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { +static void chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t a = (counter * SEGLEN) >> 16; @@ -982,16 +953,14 @@ static uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do for (unsigned i = 0; i < c; i++) //fill from start until c SEGMENT.setPixelColor(i, color3); } - - return FRAMETIME; } /* * Bicolor chase, more primary color. */ -uint16_t mode_chase_color(void) { - return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), true); +void mode_chase_color(void) { + chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), true); } static const char _data_FX_MODE_CHASE_COLOR[] PROGMEM = "Chase@!,Width;!,!,!;!"; @@ -999,8 +968,8 @@ static const char _data_FX_MODE_CHASE_COLOR[] PROGMEM = "Chase@!,Width;!,!,!;!"; /* * Primary running followed by random color. */ -uint16_t mode_chase_random(void) { - return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), false); +void mode_chase_random(void) { + chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), false); } static const char _data_FX_MODE_CHASE_RANDOM[] PROGMEM = "Chase Random@!,Width;!,,!;!"; @@ -1008,13 +977,13 @@ static const char _data_FX_MODE_CHASE_RANDOM[] PROGMEM = "Chase Random@!,Width;! /* * Primary, secondary running on rainbow. */ -uint16_t mode_chase_rainbow(void) { +void mode_chase_rainbow(void) { unsigned color_sep = 256 / SEGLEN; if (color_sep == 0) color_sep = 1; // correction for segments longer than 256 LEDs unsigned color_index = SEGENV.call & 0xFF; uint32_t color = SEGMENT.color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); - return chase(color, SEGCOLOR(0), SEGCOLOR(1), false); + chase(color, SEGCOLOR(0), SEGCOLOR(1), false); } static const char _data_FX_MODE_CHASE_RAINBOW[] PROGMEM = "Chase Rainbow@!,Width;!,!;!"; @@ -1022,13 +991,13 @@ static const char _data_FX_MODE_CHASE_RAINBOW[] PROGMEM = "Chase Rainbow@!,Width /* * Primary running on rainbow. */ -uint16_t mode_chase_rainbow_white(void) { +void mode_chase_rainbow_white(void) { uint16_t n = SEGENV.step; uint16_t m = (SEGENV.step + 1) % SEGLEN; uint32_t color2 = SEGMENT.color_wheel(((n * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); uint32_t color3 = SEGMENT.color_wheel(((m * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); - return chase(SEGCOLOR(0), color2, color3, false); + chase(SEGCOLOR(0), color2, color3, false); } static const char _data_FX_MODE_CHASE_RAINBOW_WHITE[] PROGMEM = "Rainbow Runner@!,Size;Bg;!"; @@ -1036,7 +1005,7 @@ static const char _data_FX_MODE_CHASE_RAINBOW_WHITE[] PROGMEM = "Rainbow Runner@ /* * Red - Amber - Green - Blue lights running */ -uint16_t mode_colorful(void) { +void mode_colorful(void) { unsigned numColors = 4; //3, 4, or 5 uint32_t cols[9]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC}; if (SEGMENT.intensity > 160 || SEGMENT.palette) { //palette or color @@ -1072,8 +1041,6 @@ uint16_t mode_colorful(void) { { for (unsigned j = 0; j < numColors; j++) SEGMENT.setPixelColor(i + j, cols[SEGENV.aux0 + j]); } - - return FRAMETIME; } static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2,3;!"; @@ -1081,8 +1048,8 @@ static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2, /* * Emulates a traffic light. */ -uint16_t mode_traffic_light(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_traffic_light(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; for (unsigned i=0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); uint32_t mdelay = 500; @@ -1104,8 +1071,6 @@ uint16_t mode_traffic_light(void) { if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; SEGENV.step = strip.now; } - - return FRAMETIME; } static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US style;,!;!"; @@ -1114,29 +1079,37 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st * Sec flashes running on prim. */ #define FLASH_COUNT 4 -uint16_t mode_chase_flash(void) { - if (SEGLEN <= 1) return mode_static(); - unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); +void mode_chase_flash(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; + unsigned now = strip.now; // save time for delay calculation + bool advance = true; + unsigned flash_step = SEGENV.aux1 % ((FLASH_COUNT * 2) + 1); + if (now < SEGENV.step) + advance = false; // limit update rate but render every frame for smooth transitions + else + SEGENV.aux1++; for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } + unsigned index = SEGENV.aux0; + unsigned n = index; + unsigned m = (index + 1) % SEGLEN; unsigned delay = 10 + ((30 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); if(flash_step < (FLASH_COUNT * 2)) { if(flash_step % 2 == 0) { - unsigned n = SEGENV.step; - unsigned m = (SEGENV.step + 1) % SEGLEN; SEGMENT.setPixelColor( n, SEGCOLOR(1)); SEGMENT.setPixelColor( m, SEGCOLOR(1)); delay = 20; } else { delay = 30; } - } else { - SEGENV.step = (SEGENV.step + 1) % SEGLEN; + } else if (advance) { + SEGENV.aux0 = m; // advance to next position } - return delay; + if (advance) + SEGENV.step = now + delay; // set next update time } static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!"; @@ -1144,8 +1117,14 @@ static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!"; /* * Prim flashes running, followed by random color. */ -uint16_t mode_chase_flash_random(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_chase_flash_random(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; + unsigned now = strip.now; // save time for delay calculation + bool advance = true; + if (now < SEGENV.step) { + SEGENV.call--; // revert increment to skip moving the animation forward and just render the same frame again + advance = false; + } unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGENV.aux1; i++) { @@ -1165,14 +1144,15 @@ uint16_t mode_chase_flash_random(void) { SEGMENT.setPixelColor( m, SEGCOLOR(1)); delay = 30; } - } else { + } else if (advance) { SEGENV.aux1 = (SEGENV.aux1 + 1) % SEGLEN; if (SEGENV.aux1 == 0) { SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); } } - return delay; + if (advance) + SEGENV.step = now + delay; // set next update time } static const char _data_FX_MODE_CHASE_FLASH_RANDOM[] PROGMEM = "Chase Flash Rnd@!;!,!;!"; @@ -1180,8 +1160,8 @@ static const char _data_FX_MODE_CHASE_FLASH_RANDOM[] PROGMEM = "Chase Flash Rnd@ /* * Alternating color/sec pixels running. */ -uint16_t mode_running_color(void) { - return running(SEGCOLOR(0), SEGCOLOR(1)); +void mode_running_color(void) { + running(SEGCOLOR(0), SEGCOLOR(1)); } static const char _data_FX_MODE_RUNNING_COLOR[] PROGMEM = "Chase 2@!,Width;!,!;!"; @@ -1189,7 +1169,7 @@ static const char _data_FX_MODE_RUNNING_COLOR[] PROGMEM = "Chase 2@!,Width;!,!;! /* * Random colored pixels running. ("Stream") */ -uint16_t mode_running_random(void) { +void mode_running_random(void) { uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; if (SEGENV.call == 0) SEGENV.aux0 = hw_random(); // random seed for PRNG on start @@ -1218,7 +1198,6 @@ uint16_t mode_running_random(void) { } SEGENV.aux1 = it; - return FRAMETIME; } static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;;!"; @@ -1226,21 +1205,21 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; /* * K.I.T.T. */ -uint16_t mode_larson_scanner(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_larson_scanner(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const unsigned speed = FRAMETIME * map(SEGMENT.speed, 0, 255, 96, 2); // map into useful range const unsigned pixels = SEGLEN / speed; // how many pixels to advance per frame SEGMENT.fade_out(255-SEGMENT.intensity); - if (SEGENV.step > strip.now) return FRAMETIME; // we have a pause + if (SEGENV.step > strip.now) return; // we have a pause unsigned index = SEGENV.aux1 + pixels; // are we slow enough to use frames per pixel? if (pixels == 0) { const unsigned frames = speed / SEGLEN; // how many frames per 1 pixel - if (SEGENV.step++ < frames) return FRAMETIME; + if (SEGENV.step++ < frames) return; SEGENV.step = 0; index++; } @@ -1266,7 +1245,6 @@ uint16_t mode_larson_scanner(void) { } SEGENV.aux1 = index; } - return FRAMETIME; } static const char _data_FX_MODE_LARSON_SCANNER[] PROGMEM = "Scanner@!,Trail,Delay,,,Dual,Bi-delay;!,!,!;!;;m12=0,c1=0"; @@ -1274,17 +1252,17 @@ static const char _data_FX_MODE_LARSON_SCANNER[] PROGMEM = "Scanner@!,Trail,Dela * Creates two Larson scanners moving in opposite directions * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/DualLarson.h */ -uint16_t mode_dual_larson_scanner(void){ +void mode_dual_larson_scanner(void){ SEGMENT.check1 = true; - return mode_larson_scanner(); + mode_larson_scanner(); } static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,Trail,Delay,,,Dual,Bi-delay;!,!,!;!;;m12=0,c1=0"; /* * Firing comets from one end. "Lighthouse" */ -uint16_t mode_comet(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_comet(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned counter = (strip.now * ((SEGMENT.speed >>2) +1)) & 0xFFFF; unsigned index = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) SEGENV.aux0 = index; @@ -1302,16 +1280,14 @@ uint16_t mode_comet(void) { } } SEGENV.aux0 = index++; - - return FRAMETIME; } static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!"; /* * Fireworks function. */ -uint16_t mode_fireworks() { - if (SEGLEN <= 1) return mode_static(); +void mode_fireworks() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const uint16_t width = SEGMENT.is2D() ? SEG_W : SEGLEN; const uint16_t height = SEG_H; @@ -1346,13 +1322,12 @@ uint16_t mode_fireworks() { SEGENV.aux0 = index; // remember where spark occurred } } - return FRAMETIME; } static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!;!;12;ix=192,pal=11"; //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h -uint16_t mode_rain() { - if (SEGLEN <= 1) return mode_static(); +void mode_rain() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const unsigned width = SEG_W; const unsigned height = SEG_H; SEGENV.step += FRAMETIME; @@ -1380,17 +1355,17 @@ uint16_t mode_rain() { if (SEGENV.aux0 >= width*height) SEGENV.aux0 = 0; // ignore if (SEGENV.aux1 >= width*height) SEGENV.aux1 = 0; } - return mode_fireworks(); + mode_fireworks(); } static const char _data_FX_MODE_RAIN[] PROGMEM = "Rain@!,Spawning rate;!,!;!;12;ix=128,pal=0"; /* * Fire flicker function */ -uint16_t mode_fire_flicker(void) { +void mode_fire_flicker(void) { uint32_t cycleTime = 40 + (255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; - if (SEGENV.step == it) return FRAMETIME; + if (SEGENV.step == it) return; byte w = (SEGCOLOR(0) >> 24); byte r = (SEGCOLOR(0) >> 16); @@ -1408,7 +1383,6 @@ uint16_t mode_fire_flicker(void) { } SEGENV.step = it; - return FRAMETIME; } static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;01"; @@ -1416,8 +1390,8 @@ static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;0 /* * Gradient run base function */ -uint16_t gradient_base(bool loading) { - if (SEGLEN <= 1) return mode_static(); +void gradient_base(bool loading) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t pp = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) pp = 0; @@ -1436,16 +1410,14 @@ uint16_t gradient_base(bool loading) { val = (brd > val) ? (val * 255) / brd : 255; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(0), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), uint8_t(val))); } - - return FRAMETIME; } /* * Gradient run */ -uint16_t mode_gradient(void) { - return gradient_base(false); +void mode_gradient(void) { + gradient_base(false); } static const char _data_FX_MODE_GRADIENT[] PROGMEM = "Gradient@!,Spread;!,!;!;;ix=16"; @@ -1453,16 +1425,16 @@ static const char _data_FX_MODE_GRADIENT[] PROGMEM = "Gradient@!,Spread;!,!;!;;i /* * Gradient run with hard transition */ -uint16_t mode_loading(void) { - return gradient_base(true); +void mode_loading(void) { + gradient_base(true); } static const char _data_FX_MODE_LOADING[] PROGMEM = "Loading@!,Fade;!,!;!;;ix=16"; /* * Two dots running */ -uint16_t mode_two_dots() { - if (SEGLEN <= 1) return mode_static(); +void mode_two_dots() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster uint32_t it = strip.now / map(SEGMENT.speed, 0, 255, delay<<4, delay); unsigned offset = it % SEGLEN; @@ -1477,7 +1449,6 @@ uint16_t mode_two_dots() { SEGMENT.setPixelColor(indexR, color1); SEGMENT.setPixelColor(indexB, color2); } - return FRAMETIME; } static const char _data_FX_MODE_TWO_DOTS[] PROGMEM = "Two Dots@!,Dot size,,,,,Overlay;1,2,Bg;!"; @@ -1495,7 +1466,7 @@ typedef struct Flasher { #define FLASHERS_PER_ZONE 6 #define MAX_SHIMMER 92 -uint16_t mode_fairy() { +void mode_fairy() { //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); for (unsigned i = 0; i < SEGLEN; i++) { @@ -1504,12 +1475,12 @@ uint16_t mode_fairy() { } //amount of flasher pixels depending on intensity (0: none, 255: every LED) - if (SEGMENT.intensity == 0) return FRAMETIME; + if (SEGMENT.intensity == 0) return; unsigned flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 unsigned numFlashers = (SEGLEN / flasherDistance) +1; unsigned dataSize = sizeof(flasher) * numFlashers; - if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed + if (!SEGENV.allocateData(dataSize)) return; //allocation failed Flasher* flashers = reinterpret_cast(SEGENV.data); unsigned now16 = strip.now & 0xFFFF; @@ -1564,7 +1535,6 @@ uint16_t mode_fairy() { } } } - return FRAMETIME; } static const char _data_FX_MODE_FAIRY[] PROGMEM = "Fairy@!,# of flashers;!,!;!"; @@ -1573,9 +1543,9 @@ static const char _data_FX_MODE_FAIRY[] PROGMEM = "Fairy@!,# of flashers;!,!;!"; * Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on strip.getPixelColor * Warning: Uses 4 bytes of segment data per pixel */ -uint16_t mode_fairytwinkle() { +void mode_fairytwinkle() { unsigned dataSize = sizeof(flasher) * SEGLEN; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Flasher* flashers = reinterpret_cast(SEGENV.data); unsigned now16 = strip.now & 0xFFFF; uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); @@ -1614,7 +1584,6 @@ uint16_t mode_fairytwinkle() { } SEGMENT.setPixelColor(f, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri)); } - return FRAMETIME; } static const char _data_FX_MODE_FAIRYTWINKLE[] PROGMEM = "Fairytwinkle@!,!;!,!;!;;m12=0"; //pixels @@ -1622,7 +1591,7 @@ static const char _data_FX_MODE_FAIRYTWINKLE[] PROGMEM = "Fairytwinkle@!,!;!,!;! /* * Tricolor chase function */ -uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { +void tricolor_chase(uint32_t color1, uint32_t color2) { uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1); uint32_t it = strip.now / cycleTime; // iterator unsigned width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour @@ -1637,15 +1606,14 @@ uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { SEGMENT.setPixelColor(SEGLEN - i -1, color); } - return FRAMETIME; } /* * Tricolor chase mode */ -uint16_t mode_tricolor_chase(void) { - return tricolor_chase(SEGCOLOR(2), SEGCOLOR(0)); +void mode_tricolor_chase(void) { + tricolor_chase(SEGCOLOR(2), SEGCOLOR(0)); } static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3;!"; @@ -1653,40 +1621,71 @@ static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3 /* * ICU mode */ -uint16_t mode_icu(void) { - unsigned dest = SEGENV.step & 0xFFFF; - unsigned space = (SEGMENT.intensity >> 3) +2; +void mode_icu(void) { + // states: 0 = pause1, 1 = blink, 2 = pause2, 3 = move - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + uint16_t now = strip.now; // save time for delay calculation, use low16 bits only + unsigned dest = SEGENV.aux1; + unsigned space = (SEGMENT.intensity >> 3) +2; + uint16_t state = SEGENV.step >> 16; // upper bytes of step store current state + uint16_t nextUpdate = SEGENV.step & 0xFFFF; // lower bytes store time for next update byte pindex = map(dest, 0, SEGLEN-SEGLEN/space, 0, 255); uint32_t col = SEGMENT.color_from_palette(pindex, false, false, 0); - - SEGMENT.setPixelColor(dest, col); - SEGMENT.setPixelColor(dest + SEGLEN/space, col); - - if(SEGENV.aux0 == dest) { // pause between eye movements - if(hw_random8(6) == 0) { // blink once in a while - SEGMENT.setPixelColor(dest, SEGCOLOR(1)); - SEGMENT.setPixelColor(dest + SEGLEN/space, SEGCOLOR(1)); - return 200; + uint32_t bgcol = SEGMENT.check2 ? BLACK : SEGCOLOR(1); + SEGMENT.fill(bgcol); // apply background color or clear + // draw eyes if not blinking + if (state != 1) { + SEGMENT.setPixelColor(dest, col); + SEGMENT.setPixelColor(dest + SEGLEN/space, col); + // render next position if moving + if (state == 3) { + if(SEGENV.aux0 > SEGENV.aux1) { + dest++; + } else if (SEGENV.aux0 < SEGENV.aux1) { + dest--; + } + SEGMENT.setPixelColor(dest, col); + SEGMENT.setPixelColor(dest + SEGLEN/space, col); } - SEGENV.aux0 = hw_random16(SEGLEN-SEGLEN/space); - return 1000 + hw_random16(2000); } - if(SEGENV.aux0 > SEGENV.step) { - SEGENV.step++; - dest++; - } else if (SEGENV.aux0 < SEGENV.step) { - SEGENV.step--; - dest--; + // update state + if ((int16_t)(now - nextUpdate) >= 0) { // time to update, cast to int to handle wraparound properly + switch (state) { + case 0: // pause part 1 + // first pause part finished, blink or pause some more + state++; + if(hw_random8(6) == 0) { // blink once in a while + nextUpdate = uint16_t(now + 200); + break; + } + // fall through if not blinking + case 1: // blink + // not blinking or finished blinking -> pause part 2 + nextUpdate = uint16_t(now + 500 + hw_random16(1000)); + state++; + break; + case 2: // pause part 2 + // pause finished, move + SEGENV.aux0 = hw_random16(SEGLEN-SEGLEN/space); // choose a new destination + nextUpdate = now; + state++; + break; + default: // move (state 3) + SEGENV.aux1 = dest; // update destination to moved position + nextUpdate = uint16_t(now + SPEED_FORMULA_L); + if (SEGENV.aux0 == dest) { + // reached destination + nextUpdate = uint16_t(now + 500 + hw_random16(1000)); + state = 0; + } + break; + } } - SEGMENT.setPixelColor(dest, col); - SEGMENT.setPixelColor(dest + SEGLEN/space, col); - - return SPEED_FORMULA_L; + // use upper bits of SEGENV.step to store current state, lower bits for next update time + SEGENV.step = (state << 16) | nextUpdate; } static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,!,,,,,Overlay;!,!;!"; @@ -1694,7 +1693,7 @@ static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,!,,,,,Overlay;!,!;!"; /* * Custom mode by Aircoookie. Color Wipe, but with 3 colors */ -uint16_t mode_tricolor_wipe(void) { +void mode_tricolor_wipe(void) { uint32_t cycleTime = 1000 + (255 - SEGMENT.speed)*200; uint32_t perc = strip.now % cycleTime; unsigned prog = (perc * 65535) / cycleTime; @@ -1725,8 +1724,6 @@ uint16_t mode_tricolor_wipe(void) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } - - return FRAMETIME; } static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!"; @@ -1736,7 +1733,7 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!"; * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/TriFade.h * Modified by Aircoookie */ -uint16_t mode_tricolor_fade(void) { +void mode_tricolor_fade(void) { uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1); uint32_t prog = (counter * 768) >> 16; @@ -1769,8 +1766,6 @@ uint16_t mode_tricolor_fade(void) { } SEGMENT.setPixelColor(i, color); } - - return FRAMETIME; } static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; @@ -1779,11 +1774,11 @@ static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h */ #define MAX_COMETS 8 -uint16_t mode_multi_comet(void) { +void mode_multi_comet(void) { uint32_t cycleTime = 10 + (uint32_t)(255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; - if (SEGENV.step == it) return FRAMETIME; - if (!SEGENV.allocateData(sizeof(uint16_t) * MAX_COMETS)) return mode_static(); //allocation failed + if (SEGENV.step == it) return; + if (!SEGENV.allocateData(sizeof(uint16_t) * MAX_COMETS)) FX_FALLBACK_STATIC; //allocation failed SEGMENT.fade_out(SEGMENT.intensity/2 + 128); @@ -1808,7 +1803,6 @@ uint16_t mode_multi_comet(void) { } SEGENV.step = it; - return FRAMETIME; } static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;!;1"; #undef MAX_COMETS @@ -1817,7 +1811,7 @@ static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!; * Running random pixels ("Stream 2") * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/RandomChase.h */ -uint16_t mode_random_chase(void) { +void mode_random_chase(void) { if (SEGENV.call == 0) { SEGENV.step = RGBW32(random8(), random8(), random8(), 0); SEGENV.aux0 = random16(); @@ -1843,7 +1837,6 @@ uint16_t mode_random_chase(void) { SEGENV.aux1 = it & 0xFFFF; random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG - return FRAMETIME; } static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2@!;;"; @@ -1859,11 +1852,11 @@ typedef struct Oscillator { /* / Oscillating bars of color, updated with standard framerate */ -uint16_t mode_oscillate(void) { +void mode_oscillate(void) { constexpr unsigned numOscillators = 3; constexpr unsigned dataSize = sizeof(oscillator) * numOscillators; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Oscillator* oscillators = reinterpret_cast(SEGENV.data); @@ -1905,14 +1898,12 @@ uint16_t mode_oscillate(void) { } SEGENV.step = it; - return FRAMETIME; } static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; -//TODO -uint16_t mode_lightning(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_lightning(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned ledstart = hw_random16(SEGLEN); // Determine starting location of flash unsigned ledlen = 1 + hw_random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/hw_random8(1, 3); @@ -1949,12 +1940,11 @@ uint16_t mode_lightning(void) { SEGENV.step = strip.now; } } - return FRAMETIME; } static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay;!,!;!"; // combined function from original pride and colorwaves -uint16_t mode_colorwaves_pride_base(bool isPride2015) { +void mode_colorwaves_pride_base(bool isPride2015) { unsigned duration = 10 + SEGMENT.speed; unsigned sPseudotime = SEGENV.step; unsigned sHue16 = SEGENV.aux0; @@ -2000,30 +1990,28 @@ uint16_t mode_colorwaves_pride_base(bool isPride2015) { SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; - - return FRAMETIME; } // Pride2015 // Animated, ever-changing rainbows. // by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 -uint16_t mode_pride_2015(void) { - return mode_colorwaves_pride_base(true); +void mode_pride_2015(void) { + mode_colorwaves_pride_base(true); } static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; // ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb // This function draws color waves with an ever-changing, // widely-varying set of parameters, using a color palette. -uint16_t mode_colorwaves() { - return mode_colorwaves_pride_base(false); +void mode_colorwaves() { + mode_colorwaves_pride_base(false); } static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26"; //eight colored dots, weaving in and out of sync with each other -uint16_t mode_juggle(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_juggle(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); CRGB fastled_col; @@ -2035,12 +2023,11 @@ uint16_t mode_juggle(void) { SEGMENT.setPixelColor(index, fastled_col); dothue += 32; } - return FRAMETIME; } static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=64,ix=128"; -uint16_t mode_palette() { +void mode_palette() { // Set up some compile time constants so that we can handle integer and float based modes using the same code base. #ifdef ESP8266 using mathType = int32_t; @@ -2134,7 +2121,6 @@ uint16_t mode_palette() { } } } - return FRAMETIME; } static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;ix=112,c1=0,o1=1,o2=0,o3=1"; @@ -2167,10 +2153,10 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation // There are two main parameters you can play with to control the look and // feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used // in step 3 above) (Effect Intensity = Sparking). -uint16_t mode_fire_2012() { - if (SEGLEN <= 1) return mode_static(); +void mode_fire_2012() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const unsigned strips = SEGMENT.nrOfVStrips(); - if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(strips * SEGLEN)) FX_FALLBACK_STATIC; //allocation failed byte* heat = SEGENV.data; const uint32_t it = strip.now >> 5; //div 32 @@ -2221,39 +2207,33 @@ uint16_t mode_fire_2012() { if (it != SEGENV.step) SEGENV.step = it; - - return FRAMETIME; } static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars #endif // WLED_PS_DONT_REPLACE_x_FX // colored stripes pulsing at a defined Beats-Per-Minute (BPM) -uint16_t mode_bpm() { +void mode_bpm() { uint32_t stp = (strip.now / 20) & 0xFF; uint8_t beat = beatsin8_t(SEGMENT.speed, 64, 255); for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); } - - return FRAMETIME; } static const char _data_FX_MODE_BPM[] PROGMEM = "Bpm@!;!;!;;sx=64"; -uint16_t mode_fillnoise8() { +void mode_fillnoise8() { if (SEGENV.call == 0) SEGENV.step = hw_random(); for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = perlin8(i * SEGLEN, SEGENV.step + i * SEGLEN); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } SEGENV.step += beatsin8_t(SEGMENT.speed, 1, 6); //10,1,4 - - return FRAMETIME; } static const char _data_FX_MODE_FILLNOISE8[] PROGMEM = "Fill Noise@!;!;!"; -uint16_t mode_noise16_1() { +void mode_noise16_1() { unsigned scale = 320; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed/16); @@ -2268,13 +2248,11 @@ uint16_t mode_noise16_1() { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } - - return FRAMETIME; } static const char _data_FX_MODE_NOISE16_1[] PROGMEM = "Noise 1@!;!;!;;pal=20"; -uint16_t mode_noise16_2() { +void mode_noise16_2() { unsigned scale = 1000; // the "zoom factor" for the noise SEGENV.step += (1 + (SEGMENT.speed >> 1)); @@ -2286,13 +2264,11 @@ uint16_t mode_noise16_2() { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } - - return FRAMETIME; } static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!;!;!;;pal=43"; -uint16_t mode_noise16_3() { +void mode_noise16_3() { unsigned scale = 800; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed); @@ -2307,28 +2283,29 @@ uint16_t mode_noise16_3() { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } - - return FRAMETIME; } static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!;;pal=35"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino -uint16_t mode_noise16_4() { +void mode_noise16_4() { uint32_t stp = (strip.now * SEGMENT.speed) >> 7; for (unsigned i = 0; i < SEGLEN; i++) { int index = perlin16(uint32_t(i) << 12, stp); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } - return FRAMETIME; } static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!;!;!;;pal=26"; //based on https://gist.github.com/kriegsman/5408ecd397744ba0393e -uint16_t mode_colortwinkle() { +void mode_colortwinkle() { unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed + + //limit update rate + if (strip.now - SEGENV.step < FRAMETIME_FIXED) return; + SEGENV.step = strip.now; CRGBW col, prev; fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); @@ -2374,13 +2351,12 @@ uint16_t mode_colortwinkle() { } } } - return FRAMETIME_FIXED; } static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade speed,Spawn speed;;!;;m12=0"; //pixels //Calm effect, like a lake at night -uint16_t mode_lake() { +void mode_lake() { unsigned sp = SEGMENT.speed/10; int wave1 = beatsin8_t(sp +2, -64,64); int wave2 = beatsin8_t(sp +1, -64,64); @@ -2392,8 +2368,6 @@ uint16_t mode_lake() { uint8_t lum = (index > wave3) ? index - wave3 : 0; SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0, lum)); } - - return FRAMETIME; } static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!;Fx;!"; @@ -2401,9 +2375,9 @@ static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!;Fx;!"; // meteor effect & meteor smooth (merged by @dedehai) // send a meteor from begining to to the end of the strip with a trail that randomly decays. // adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain -uint16_t mode_meteor() { - if (SEGLEN <= 1) return mode_static(); - if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed +void mode_meteor() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; + if (!SEGENV.allocateData(SEGLEN)) FX_FALLBACK_STATIC; //allocation failed const bool meteorSmooth = SEGMENT.check3; byte* trail = SEGENV.data; @@ -2464,14 +2438,13 @@ uint16_t mode_meteor() { } SEGENV.step += SEGMENT.speed +1; - return FRAMETIME; } static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient,,Smooth;;!;1"; //Railway Crossing / Christmas Fairy lights -uint16_t mode_railway() { - if (SEGLEN <= 1) return mode_static(); +void mode_railway() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned dur = (256 - SEGMENT.speed) * 40; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; if (SEGENV.step > dur) @@ -2496,7 +2469,6 @@ uint16_t mode_railway() { } } SEGENV.step += FRAMETIME; - return FRAMETIME; } static const char _data_FX_MODE_RAILWAY[] PROGMEM = "Railway@!,Smoothness;1,2;!;;pal=3"; @@ -2517,11 +2489,11 @@ typedef struct Ripple { #else #define MAX_RIPPLES 100 #endif -static uint16_t ripple_base(uint8_t blurAmount = 0) { +static void ripple_base(uint8_t blurAmount = 0) { unsigned maxRipples = min(1 + (int)(SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 unsigned dataSize = sizeof(ripple) * maxRipples; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); @@ -2566,25 +2538,24 @@ static uint16_t ripple_base(uint8_t blurAmount = 0) { } } SEGMENT.blur(blurAmount); - return FRAMETIME; } #undef MAX_RIPPLES -uint16_t mode_ripple(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_ripple(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; if(SEGMENT.custom1 || SEGMENT.check2) // blur or overlay SEGMENT.fade_out(250); else SEGMENT.fill(SEGCOLOR(1)); - return ripple_base(SEGMENT.custom1>>1); + ripple_base(SEGMENT.custom1>>1); } static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,Blur,,,,Overlay;,!;!;12;c1=0"; -uint16_t mode_ripple_rainbow(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_ripple_rainbow(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; if (SEGENV.call ==0) { SEGENV.aux0 = hw_random8(); SEGENV.aux1 = hw_random8(); @@ -2597,7 +2568,7 @@ uint16_t mode_ripple_rainbow(void) { SEGENV.aux0--; } SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux0),BLACK,uint8_t(235))); - return ripple_base(); + ripple_base(); } static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wave #;;!;12"; @@ -2668,7 +2639,7 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) // "CalculateOneTwinkle" on each pixel. It then displays // either the twinkle color of the background color, // whichever is brighter. -static uint16_t twinklefox_base(bool cat) +static void twinklefox_base(bool cat) { // "PRNG16" is the pseudorandom number generator // It MUST be reset to the same starting value each time @@ -2724,25 +2695,23 @@ static uint16_t twinklefox_base(bool cat) SEGMENT.setPixelColor(i, bg); } } - return FRAMETIME; } - -uint16_t mode_twinklefox() +void mode_twinklefox() { - return twinklefox_base(false); + twinklefox_base(false); } static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate,,,,Cool;!,!;!"; -uint16_t mode_twinklecat() +void mode_twinklecat() { - return twinklefox_base(true); + twinklefox_base(true); } static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool,Reverse;!,!;!"; -uint16_t mode_halloween_eyes() +void mode_halloween_eyes() { enum eyeState : uint8_t { initializeOn = 0, @@ -2764,14 +2733,14 @@ uint16_t mode_halloween_eyes() uint32_t blinkEndTime; }; - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const unsigned maxWidth = strip.isMatrix ? SEG_W : SEGLEN; const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEG_W>>4: SEGLEN>>5); const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; unsigned eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; - if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short + if (eyeLength >= maxWidth) FX_FALLBACK_STATIC; //bail if segment too short - if (!SEGENV.allocateData(sizeof(EyeData))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(EyeData))) FX_FALLBACK_STATIC; //allocation failed EyeData& data = *reinterpret_cast(SEGENV.data); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background @@ -2893,14 +2862,12 @@ uint16_t mode_halloween_eyes() } data.startTime = strip.now; } - - return FRAMETIME; } static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye off time,Eye on time,,,,,Overlay;!,!;!;12"; //Speed slider sets amount of LEDs lit, intensity sets unlit -uint16_t mode_static_pattern() +void mode_static_pattern() { unsigned lit = 1 + SEGMENT.speed; unsigned unlit = 1 + SEGMENT.intensity; @@ -2915,13 +2882,11 @@ uint16_t mode_static_pattern() drawingLit = !drawingLit; } } - - return FRAMETIME; } static const char _data_FX_MODE_STATIC_PATTERN[] PROGMEM = "Solid Pattern@Fg size,Bg size;Fg,!;!;;pal=0"; -uint16_t mode_tri_static_pattern() +void mode_tri_static_pattern() { unsigned segSize = (SEGMENT.intensity >> 5) +1; unsigned currSeg = 0; @@ -2941,15 +2906,13 @@ uint16_t mode_tri_static_pattern() currSegCount = 0; } } - - return FRAMETIME; } static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tri@,Size;1,2,3;;;pal=0"; -static uint16_t spots_base(uint16_t threshold) +static void spots_base(uint16_t threshold) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); unsigned maxZones = SEGLEN >> 2; @@ -2970,26 +2933,24 @@ static uint16_t spots_base(uint16_t threshold) } } } - - return FRAMETIME; } //Intensity slider sets number of "lights", speed sets LEDs per light -uint16_t mode_spots() +void mode_spots() { - return spots_base((255 - SEGMENT.speed) << 8); + spots_base((255 - SEGMENT.speed) << 8); } static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width,,,,,Overlay;!,!;!"; //Intensity slider sets number of "lights", LEDs per light fade in and out -uint16_t mode_spots_fade() +void mode_spots_fade() { unsigned counter = strip.now * ((SEGMENT.speed >> 2) +8); unsigned t = triwave16(counter); unsigned tr = (t >> 1) + (t >> 2); - return spots_base(tr); + spots_base(tr); } static const char _data_FX_MODE_SPOTS_FADE[] PROGMEM = "Spots Fade@Spread,Width,,,,,Overlay;!,!;!"; @@ -3003,13 +2964,13 @@ typedef struct Ball { /* * Bouncing Balls Effect */ -uint16_t mode_bouncing_balls(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_bouncing_balls(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; //allocate segment data const unsigned strips = SEGMENT.nrOfVStrips(); // adapt for 2D const size_t maxNumBalls = 16; unsigned dataSize = sizeof(ball) * maxNumBalls; - if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize * strips)) FX_FALLBACK_STATIC; //allocation failed Ball* balls = reinterpret_cast(SEGENV.data); @@ -3071,8 +3032,6 @@ uint16_t mode_bouncing_balls(void) { for (unsigned stripNr=0; stripNr(SEGENV.data); @@ -3172,8 +3131,6 @@ static uint16_t rolling_balls(void) { balls[i].lastBounceUpdate = strip.now; balls[i].height = thisHeight; } - - return FRAMETIME; } static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar #endif // WLED_PS_DONT_REPLACE_1D_FX @@ -3201,7 +3158,7 @@ typedef struct PacManChars { bool eaten; // used for power dots only } pacmancharacters_t; -static uint16_t mode_pacman(void) { +static void mode_pacman(void) { constexpr unsigned ORANGEYELLOW = 0xFFCC00; constexpr unsigned PURPLEISH = 0xB000B0; constexpr unsigned ORANGEISH = 0xFF8800; @@ -3221,7 +3178,7 @@ static uint16_t mode_pacman(void) { // Allocate segment data unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character - if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); + if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; pacmancharacters_t *character = reinterpret_cast(SEGENV.data); // Calculate when blue ghosts start blinking. @@ -3369,7 +3326,6 @@ static uint16_t mode_pacman(void) { } SEGMENT.blur(SEGMENT.custom2>>1); - return FRAMETIME; } static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Smear,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0"; @@ -3377,8 +3333,8 @@ static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots, /* * Sinelon stolen from FASTLED examples */ -static uint16_t sinelon_base(bool dual, bool rainbow=false) { - if (SEGLEN <= 1) return mode_static(); +static void sinelon_base(bool dual, bool rainbow=false) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; SEGMENT.fade_out(SEGMENT.intensity); unsigned pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1); if (SEGENV.call == 0) SEGENV.aux0 = pos; @@ -3407,25 +3363,23 @@ static uint16_t sinelon_base(bool dual, bool rainbow=false) { } SEGENV.aux0 = pos; } - - return FRAMETIME; } -uint16_t mode_sinelon(void) { - return sinelon_base(false); +void mode_sinelon(void) { + sinelon_base(false); } static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon@!,Trail;!,!,!;!"; -uint16_t mode_sinelon_dual(void) { - return sinelon_base(true); +void mode_sinelon_dual(void) { + sinelon_base(true); } static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual@!,Trail;!,!,!;!"; -uint16_t mode_sinelon_rainbow(void) { - return sinelon_base(false, true); +void mode_sinelon_rainbow(void) { + sinelon_base(false, true); } static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!"; @@ -3435,7 +3389,7 @@ void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) { } //Glitter with palette background, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6 -uint16_t mode_glitter() +void mode_glitter() { if (!SEGMENT.check2) { // use "* Color 1" palette for solid background (replacing "Solid glitter") unsigned counter = 0; @@ -3444,7 +3398,7 @@ uint16_t mode_glitter() counter = counter >> 8; } - bool noWrap = (strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)); + bool noWrap = (paletteBlend == 2 || (paletteBlend == 0 && SEGMENT.speed == 0)); for (unsigned i = 0; i < SEGLEN; i++) { unsigned colorIndex = (i * 255 / SEGLEN) - counter; if (noWrap) colorIndex = map(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end" @@ -3452,17 +3406,15 @@ uint16_t mode_glitter() } } glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); - return FRAMETIME; } static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;,,Glitter color;!;;pal=11,m12=0"; //pixels //Solid colour background with glitter (can be replaced by Glitter) -uint16_t mode_solid_glitter() +void mode_solid_glitter() { SEGMENT.fill(SEGCOLOR(0)); glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); - return FRAMETIME; } static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;Bg,,Glitter color;;;m12=0"; @@ -3480,14 +3432,14 @@ typedef struct Spark { * POPCORN * modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h */ -uint16_t mode_popcorn(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_popcorn(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); unsigned usablePopcorns = maxNumPopcorn; if (usablePopcorns * strips * sizeof(spark) > FAIR_DATA_PER_SEG) usablePopcorns = FAIR_DATA_PER_SEG / (strips * sizeof(spark)) + 1; // at least 1 popcorn per vstrip unsigned dataSize = sizeof(spark) * usablePopcorns; // on a matrix 64x64 this could consume a little less than 27kB when Bar expansion is used - if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize * strips)) FX_FALLBACK_STATIC; //allocation failed Spark* popcorn = reinterpret_cast(SEGENV.data); @@ -3536,8 +3488,6 @@ uint16_t mode_popcorn(void) { for (unsigned stripNr=0; stripNr maxStars) numStars = maxStars; unsigned dataSize = sizeof(star) * numStars; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed uint32_t it = strip.now; @@ -3761,7 +3714,6 @@ uint16_t mode_starburst(void) { } } } - return FRAMETIME; } #undef STARBURST_MAX_FRAG static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments,,,,,Overlay;,!;!;;pal=11,m12=0"; @@ -3773,9 +3725,9 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc * adapted from: http://www.anirama.com/1000leds/1d-fireworks/ * adapted for 2D WLED by blazoncek (Blaz Kristan (AKA blazoncek)) */ -uint16_t mode_exploding_fireworks(void) +void mode_exploding_fireworks(void) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const int cols = SEGMENT.is2D() ? SEG_W : 1; const int rows = SEGMENT.is2D() ? SEG_H : SEGLEN; @@ -3788,7 +3740,7 @@ uint16_t mode_exploding_fireworks(void) unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); unsigned dataSize = sizeof(spark) * numSparks; - if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize + sizeof(float))) FX_FALLBACK_STATIC; //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); if (dataSize != SEGENV.aux1) { //reset to flare if sparks were reallocated (it may be good idea to reset segment if bounds change) @@ -3900,8 +3852,6 @@ uint16_t mode_exploding_fireworks(void) SEGENV.aux0 = 0; //back to flare } } - - return FRAMETIME; } #undef MAX_SPARKS static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128"; @@ -3911,14 +3861,14 @@ static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gr * Drip Effect * ported of: https://www.youtube.com/watch?v=sru2fXh4r7k */ -uint16_t mode_drip(void) +void mode_drip(void) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); const int maxNumDrops = 4; unsigned dataSize = sizeof(spark) * maxNumDrops; - if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize * strips)) FX_FALLBACK_STATIC; //allocation failed Spark* drops = reinterpret_cast(SEGENV.data); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); @@ -3988,8 +3938,6 @@ uint16_t mode_drip(void) for (unsigned stripNr=0; stripNr(SEGENV.data); //if (SEGENV.call == 0) SEGMENT.fill(SEGCOLOR(1)); // will fill entire segment (1D or 2D), then use drop->step = 0 below @@ -4078,8 +4026,6 @@ uint16_t mode_tetrix(void) { for (unsigned stripNr=0; stripNr size) SEGENV.aux1 -= size; else SEGENV.aux1 = 0; if (SEGENV.aux1 < active_leds) SEGENV.aux1 = active_leds; } - - return FRAMETIME; } static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!"; @@ -4165,7 +4107,7 @@ static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One * Modulates the brightness similar to a heartbeat * (unimplemented?) tries to draw an ECG approximation on a 2D matrix */ -uint16_t mode_heartbeat(void) { +void mode_heartbeat(void) { unsigned bpm = 40 + (SEGMENT.speed >> 3); uint32_t msPerBeat = (60000L / bpm); uint32_t secondBeat = (msPerBeat / 3); @@ -4188,8 +4130,6 @@ uint16_t mode_heartbeat(void) { for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), uint8_t(255 - (SEGENV.aux1 >> 8)))); } - - return FRAMETIME; } static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!;!;01;m12=1"; @@ -4234,7 +4174,7 @@ static CRGB pacifica_one_layer(uint16_t i, const CRGBPalette16& p, uint16_t cist return CRGB(ColorFromPalette(p, sindex8, bri, LINEARBLEND)); } -uint16_t mode_pacifica() +void mode_pacifica() { uint32_t nowOld = strip.now; @@ -4306,7 +4246,6 @@ uint16_t mode_pacifica() } strip.now = nowOld; - return FRAMETIME; } static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica@!,Angle;;!;;pal=51"; @@ -4314,8 +4253,8 @@ static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica@!,Angle;;!;;pal=5 /* * Mode simulates a gradual sunrise */ -uint16_t mode_sunrise() { - if (SEGLEN <= 1) return mode_static(); +void mode_sunrise() { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; //speed 0 - static sun //speed 1 - 60: sunrise time in minutes //speed 60 - 120 : sunset time in minutes - 60; @@ -4356,8 +4295,6 @@ uint16_t mode_sunrise() { SEGMENT.setPixelColor(i, c); SEGMENT.setPixelColor(SEGLEN - i - 1, c); } - - return FRAMETIME; } static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],Width;;!;;pal=35,sx=60"; @@ -4365,7 +4302,7 @@ static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],Width;;! /* * Effects by Andrew Tuline */ -static uint16_t phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. +static void phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. unsigned allfreq = 16; // Base frequency. float *phase = reinterpret_cast(&SEGENV.step); // Phase change value gets calculated (float fits into unsigned long). @@ -4386,24 +4323,22 @@ static uint16_t phased_base(uint8_t moder) { // We're making si index += 256 / SEGLEN; if (SEGLEN > 256) index ++; // Correction for segments longer than 256 LEDs } - - return FRAMETIME; } -uint16_t mode_phased(void) { - return phased_base(0); +void mode_phased(void) { + phased_base(0); } static const char _data_FX_MODE_PHASED[] PROGMEM = "Phased@!,!;!,!;!"; -uint16_t mode_phased_noise(void) { - return phased_base(1); +void mode_phased_noise(void) { + phased_base(1); } static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise@!,!;!,!;!"; -uint16_t mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. +void mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. @@ -4415,18 +4350,17 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine } random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG - return FRAMETIME; } static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,!;!;;m12=0"; // Peaceful noise that's slow and with gradually changing palettes. Does not support WLED palettes or default colours or controls. -uint16_t mode_noisepal(void) { // Slow noise palette by Andrew Tuline. +void mode_noisepal(void) { // Slow noise palette by Andrew Tuline. unsigned scale = 15 + (SEGMENT.intensity >> 2); //default was 30 //#define scale 30 unsigned dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes (2 * 16 * 3 = 96 bytes) - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); @@ -4450,8 +4384,6 @@ uint16_t mode_noisepal(void) { // Slow noise } SEGENV.aux0 += beatsin8_t(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. - - return FRAMETIME; } static const char _data_FX_MODE_NOISEPAL[] PROGMEM = "Noise Pal@!,Scale;;!"; @@ -4459,7 +4391,7 @@ static const char _data_FX_MODE_NOISEPAL[] PROGMEM = "Noise Pal@!,Scale;;!"; // Sine waves that have controllable phase change speed, frequency and cutoff. By Andrew Tuline. // SEGMENT.speed ->Speed, SEGMENT.intensity -> Frequency (SEGMENT.fft1 -> Color change, SEGMENT.fft2 -> PWM cutoff) // -uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline +void mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline //#define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned subtraction macro. if result <0, then => 0 unsigned colorIndex = strip.now /32;//(256 - SEGMENT.fft1); // Amount of colour change. @@ -4472,8 +4404,6 @@ uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tul //setPixCol(i, i*colorIndex/255, pixBri); SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*colorIndex/255, false, PALETTE_SOLID_WRAP, 0), pixBri)); } - - return FRAMETIME; } static const char _data_FX_MODE_SINEWAVE[] PROGMEM = "Sine@!,Scale;;!"; @@ -4481,7 +4411,7 @@ static const char _data_FX_MODE_SINEWAVE[] PROGMEM = "Sine@!,Scale;;!"; /* * Best of both worlds from Palette and Spot effects. By Aircoookie */ -uint16_t mode_flow(void) +void mode_flow(void) { unsigned counter = 0; if (SEGMENT.speed != 0) @@ -4491,27 +4421,24 @@ uint16_t mode_flow(void) } unsigned maxZones = SEGLEN / 6; //only looks good if each zone has at least 6 LEDs - unsigned zones = (SEGMENT.intensity * maxZones) >> 8; + int zones = (SEGMENT.intensity * maxZones) >> 8; if (zones & 0x01) zones++; //zones must be even if (zones < 2) zones = 2; - unsigned zoneLen = SEGLEN / zones; - unsigned offset = (SEGLEN - zones * zoneLen) >> 1; + int zoneLen = SEGLEN / zones; + zones += 2; //add two extra zones to cover beginning and end of segment (compensate integer truncation) + int offset = ((int)SEGLEN - (zones * zoneLen)) / 2; // center the zones on the segment (can not use bit shift on negative number) - SEGMENT.fill(SEGMENT.color_from_palette(-counter, false, true, 255)); - - for (unsigned z = 0; z < zones; z++) + for (int z = 0; z < zones; z++) { - unsigned pos = offset + z * zoneLen; - for (unsigned i = 0; i < zoneLen; i++) + int pos = offset + z * zoneLen; + for (int i = 0; i < zoneLen; i++) { unsigned colorIndex = (i * 255 / zoneLen) - counter; - unsigned led = (z & 0x01) ? i : (zoneLen -1) -i; + int led = (z & 0x01) ? i : (zoneLen -1) -i; if (SEGMENT.reverse) led = (zoneLen -1) -led; SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, true, 255)); } } - - return FRAMETIME; } static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //vertical @@ -4520,9 +4447,9 @@ static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //ver * Dots waving around in a sine/pendulum motion. * Little pixel birds flying in a circle. By Aircoookie */ -uint16_t mode_chunchun(void) +void mode_chunchun(void) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; SEGMENT.fade_out(254); // add a bit of trail unsigned counter = strip.now * (6 + (SEGMENT.speed >> 4)); unsigned numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment @@ -4536,7 +4463,6 @@ uint16_t mode_chunchun(void) bird = constrain(bird, 0U, SEGLEN-1U); SEGMENT.setPixelColor(bird, SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0)); // no palette wrapping } - return FRAMETIME; } static const char _data_FX_MODE_CHUNCHUN[] PROGMEM = "Chunchun@!,Gap size;!,!;!"; @@ -4571,15 +4497,15 @@ typedef struct Spotlight { * * By Steve Pomeroy @xxv */ -uint16_t mode_dancing_shadows(void) +void mode_dancing_shadows(void) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; SEGENV.aux0 = numSpotlights; unsigned dataSize = sizeof(spotlight) * numSpotlights; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Spotlight* spotlights = reinterpret_cast(SEGENV.data); SEGMENT.fill(BLACK); @@ -4683,8 +4609,6 @@ uint16_t mode_dancing_shadows(void) } } } - - return FRAMETIME; } static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!;!"; #endif // WLED_PS_DONT_REPLACE_1D_FX @@ -4693,7 +4617,7 @@ static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# Imitates a washing machine, rotating same waves forward, then pause, then backward. By Stefan Seegel */ -uint16_t mode_washing_machine(void) { +void mode_washing_machine(void) { int speed = tristate_square8(strip.now >> 7, 90, 15); SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); @@ -4702,8 +4626,6 @@ uint16_t mode_washing_machine(void) { uint8_t col = sin8_t(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); } - - return FRAMETIME; } static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,!;;!"; @@ -4712,12 +4634,11 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Image effect Draws a .gif image from filesystem on the matrix/strip */ -uint16_t mode_image(void) { +void mode_image(void) { #ifndef WLED_ENABLE_GIF - return mode_static(); + FX_FALLBACK_STATIC; #else renderImageToSegment(SEGMENT); - return FRAMETIME; #endif // if (status != 0 && status != 254 && status != 255) { // Serial.print("GIF renderer return: "); @@ -4730,10 +4651,10 @@ static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,Blur,;;;12;sx=128,ix= Blends random colors across palette Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e */ -uint16_t mode_blends(void) { +void mode_blends(void) { unsigned pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN; unsigned dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); unsigned shift = (strip.now * ((SEGMENT.speed >> 3) +1)) >> 8; @@ -4748,8 +4669,6 @@ uint16_t mode_blends(void) { SEGMENT.setPixelColor(i, pixels[offset++]); if (offset >= pixelLen) offset = 0; } - - return FRAMETIME; } static const char _data_FX_MODE_BLENDS[] PROGMEM = "Blends@Shift speed,Blend speed;;!"; @@ -4779,11 +4698,11 @@ typedef struct TvSim { uint16_t pb = 0; } tvSim; -uint16_t mode_tv_simulator(void) { +void mode_tv_simulator(void) { int nr, ng, nb, r, g, b, i, hue; uint8_t sat, bri, j; - if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(tvSim))) FX_FALLBACK_STATIC; //allocation failed TvSim* tvSimulator = reinterpret_cast(SEGENV.data); uint8_t colorSpeed = map(SEGMENT.speed, 0, UINT8_MAX, 1, 20); @@ -4877,8 +4796,6 @@ uint16_t mode_tv_simulator(void) { tvSimulator->pb = nb; SEGENV.aux0 = 0; } - - return FRAMETIME; } static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01"; @@ -4993,11 +4910,11 @@ class AuroraWave { bool stillAlive() { return alive; } }; -uint16_t mode_aurora(void) { +void mode_aurora(void) { AuroraWave* waves; SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); // aux1 = Wavecount if (!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { - return mode_static(); + FX_FALLBACK_STATIC; } waves = reinterpret_cast(SEGENV.data); @@ -5026,11 +4943,84 @@ uint16_t mode_aurora(void) { SEGMENT.setPixelColor(i, mixedRgb); } - return FRAMETIME; } - static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pal=50"; + +/** Softly floating colorful clouds. + * This is a very smooth effect that moves colorful clouds randomly around the LED strip. + * It was initially intended for rather unobtrusive ambient lights (with very slow speed settings). + * Nevertheless, it appears completely different and quite vibrant when the sliders are moved near + * to their limits. No matter in which direction or in which combination... + * Ported to WLED from https://github.com/JoaDick/EyeCandy/blob/master/ColorClouds.h + */ +void mode_ColorClouds() +{ + // Set random start points for clouds and color. + if (SEGENV.call == 0) { + SEGENV.aux0 = hw_random16(); + SEGENV.aux1 = hw_random16(); + } + const uint32_t volX0 = SEGENV.aux0; + const uint32_t hueX0 = SEGENV.aux1; + const uint8_t hueOffset0 = volX0 + hueX0; // derive a 3rd random number + + // Makes a very soft wraparound of the color palette by putting more emphasis on the begin & end + // of the palette (or on the red'ish colors in case of a rainbow spectrum). + // This gives the effect oftentimes an even more calm perception. + const bool cozy = SEGMENT.check3; + + // Higher values make the clouds move faster. + const uint32_t volSpeed = 1 + SEGMENT.speed; + + // Higher values make the color change faster. + const uint32_t hueSpeed = 1 + SEGMENT.intensity; + + // Higher values make more clouds (but smaller ones). + const uint32_t volSqueeze = 8 + SEGMENT.custom1; + + // Higher values make the clouds more colorful. + const uint32_t hueSqueeze = SEGMENT.custom2; + + // Higher values make larger gaps between the clouds. + const int32_t volCutoff = 12500 + SEGMENT.custom3 * 900; + const int32_t volSaturate = 52000; + // Note: When adjusting these calculations, ensure that volCutoff is always smaller than volSaturate. + + const uint32_t now = strip.now; + const uint32_t volT = now * volSpeed / 8; + const uint32_t hueT = now * hueSpeed / 8; + const uint8_t hueOffset = beat88(64) >> 8; + + for (int i = 0; i < SEGLEN; i++) { + const uint32_t volX = i * volSqueeze * 64; + int32_t vol = perlin16(volX0 + volX, volT); + vol = map(vol, volCutoff, volSaturate, 0, 255); + vol = constrain(vol, 0, 255); + + const uint32_t hueX = i * hueSqueeze * 8; + uint8_t hue = perlin16(hueX0 + hueX, hueT) >> 7; + hue += hueOffset0; + hue += hueOffset; + if (cozy) { + hue = cos8_t(128 + hue / 2); + } + + uint32_t pixel; + if (SEGMENT.palette) { pixel = SEGMENT.color_from_palette(hue, false, true, 0, vol); } + else { hsv2rgb(CHSV32(hue, 255, vol), pixel); } + + // Suppress extremely dark pixels to avoid flickering of plain r/g/b. + if (int(R(pixel)) + G(pixel) + B(pixel) <= 2) { + pixel = 0; + } + + SEGMENT.setPixelColor(i, pixel); + } +} +static const char _data_FX_MODE_COLORCLOUDS[] PROGMEM = "Color Clouds@!,!,Clouds,Colors,Distance,,,Cozy;;!;;sx=24,ix=32,c1=48,c2=64,c3=12,pal=0"; + + // WLED-SR effects ///////////////////////// @@ -5038,16 +5028,14 @@ static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pa ///////////////////////// // 16 bit perlinmove. Use Perlin Noise instead of sinewaves for movement. By Andrew Tuline. // Controls are speed, # of pixels, faderate. -uint16_t mode_perlinmove(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_perlinmove(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { unsigned locn = perlin16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. unsigned pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); } - - return FRAMETIME; } // mode_perlinmove() static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixels,Fade rate;!,!;!"; @@ -5056,7 +5044,7 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel // Waveins // ///////////////////////// // Uses beatsin8() + phase shifting. By: Andrew Tuline -uint16_t mode_wavesins(void) { +void mode_wavesins(void) { for (unsigned i = 0; i < SEGLEN; i++) { uint8_t bri = sin8_t(strip.now/4 + i * SEGMENT.intensity); @@ -5064,8 +5052,6 @@ uint16_t mode_wavesins(void) { //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); } - - return FRAMETIME; } // mode_waveins() static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness variation,Starting color,Range of colors,Color variation;!;!"; @@ -5074,8 +5060,8 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari // Flow Stripe // ////////////////////////////// // By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline, fixed by @DedeHai -uint16_t mode_FlowStripe(void) { - if (SEGLEN <= 1) return mode_static(); +void mode_FlowStripe(void) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; const int hl = SEGLEN * 10 / 13; uint8_t hue = strip.now / (SEGMENT.speed+1); uint32_t t = strip.now / (SEGMENT.intensity/8+1); @@ -5087,8 +5073,6 @@ uint16_t mode_FlowStripe(void) { byte b = sin8_t(c + t/8); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(b + hue, false, true, 3)); } - - return FRAMETIME; } // mode_FlowStripe() static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;!;pal=11"; @@ -5097,8 +5081,8 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef It can be used as an overlay to other effects or standalone by DedeHai (Damian Schneider), based on idea from @Charming-Lime (#4905) */ -uint16_t mode_shimmer() { - if(!SEGENV.allocateData(sizeof(uint32_t))) { return mode_static(); } +void mode_shimmer() { + if(!SEGENV.allocateData(sizeof(uint32_t))) { FX_FALLBACK_STATIC; } uint32_t* lastTime = reinterpret_cast(SEGENV.data); uint32_t radius = (SEGMENT.custom1 * SEGLEN >> 7) + 1; // [1, 2*SEGLEN+1] pixels @@ -5160,8 +5144,6 @@ uint16_t mode_shimmer() { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } } - - return FRAMETIME; } static const char _data_FX_MODE_SHIMMER[] PROGMEM = "Shimmer@Speed,Interval,Size,Granular,Flow,Zebra,Reverse,Sporadic;Fx,Bg,Cx;!;1;pal=15,sx=220,ix=10,c2=0,c3=0"; @@ -5171,8 +5153,8 @@ static const char _data_FX_MODE_SHIMMER[] PROGMEM = "Shimmer@Speed,Interval,Size // Black hole -uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5196,8 +5178,6 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100); - - return FRAMETIME; } // mode_2DBlackHole() static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.,Solid,,Blur;!;!;2;pal=11"; @@ -5205,8 +5185,8 @@ static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Ou //////////////////////////// // 2D Colored Bursts // //////////////////////////// -uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5248,8 +5228,6 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so } } SEGMENT.blur(SEGMENT.custom3>>1, SEGMENT.check2); - - return FRAMETIME; } // mode_2DColoredBursts() static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Speed,# of lines,,,Blur,Gradient,Smear,Dots;;!;2;c3=16"; @@ -5257,8 +5235,8 @@ static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Spee ///////////////////// // 2D DNA // ///////////////////// -uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5269,16 +5247,14 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.setPixelColorXY(i, beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10+128), LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity / (8 - (SEGMENT.check1 * 2)), SEGMENT.check1); - - return FRAMETIME; } // mode_2Ddna() static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur,,,,Smear;;!;2;ix=0"; ///////////////////////// // 2D DNA Spiral // ///////////////////////// -uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5316,8 +5292,6 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma } } SEGMENT.blur(((uint16_t)SEGMENT.custom1 * 3) / (6 + SEGMENT.check1), SEGMENT.check1); - - return FRAMETIME; } // mode_2DDNASpiral() static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed,Y frequency,Blur,,,Smear;;!;2;c1=0"; @@ -5325,8 +5299,8 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed ///////////////////////// // 2D Drift // ///////////////////////// -uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5346,8 +5320,6 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>(3 - SEGMENT.check2), SEGMENT.check2); - - return FRAMETIME; } // mode_2DDrift() static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur,,,,Twin,Smear;;!;2;ix=0"; @@ -5355,8 +5327,8 @@ static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur,, ////////////////////////// // 2D Firenoise // ////////////////////////// -uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5380,8 +5352,6 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline SEGMENT.setPixelColorXY(j, i, ColorFromPalette(pal, min(i*indexx/11, 225U), i*255/rows, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. } // for i } // for j - - return FRAMETIME; } // mode_2Dfirenoise() static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale,,,,Palette;;!;2;pal=66"; @@ -5389,8 +5359,8 @@ static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y sca ////////////////////////////// // 2D Frizzles // ////////////////////////////// -uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5402,7 +5372,6 @@ uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.so ColorFromPalette(SEGPALETTE, beatsin8_t(12, 0, 255), 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom1 >> (3 + SEGMENT.check1), SEGMENT.check1); - return FRAMETIME; } // mode_2DFrizzles() static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y frequency,Blur,,,Smear;;!;2"; @@ -5414,13 +5383,13 @@ typedef struct Cell { uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2; } Cell; -uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ +void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ // and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W, rows = SEG_H; const unsigned maxIndex = cols * rows; - if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed + if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) FX_FALLBACK_STATIC; // allocation failed Cell *cells = reinterpret_cast (SEGENV.data); @@ -5479,7 +5448,6 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } } } - return FRAMETIME; } // Repeat detection @@ -5563,7 +5531,6 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: ++generation; SEGENV.step = strip.now; } - return FRAMETIME; } // mode_2Dgameoflife() static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128"; @@ -5571,8 +5538,8 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,, ///////////////////////// // 2D Hiphotic // ///////////////////////// -uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5583,8 +5550,6 @@ uint16_t mode_2DHiphotic() { // By: ldirko https://edit SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8_t(cos8_t(x * SEGMENT.speed/16 + a / 3) + sin8_t(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); } } - - return FRAMETIME; } // mode_2DHiphotic() static const char _data_FX_MODE_2DHIPHOTIC[] PROGMEM = "Hiphotic@X scale,Y scale,,,Speed;!;!;2"; @@ -5603,13 +5568,13 @@ typedef struct Julia { float xymag; } julia; -uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; - if (!SEGENV.allocateData(sizeof(julia))) return mode_static(); + if (!SEGENV.allocateData(sizeof(julia))) FX_FALLBACK_STATIC; Julia* julias = reinterpret_cast(SEGENV.data); float reAl; @@ -5701,8 +5666,6 @@ uint16_t mode_2DJulia(void) { // An animated Julia set } if(SEGMENT.check1) SEGMENT.blur(100, true); - - return FRAMETIME; } // mode_2DJulia() static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size, Blur;!;!;2;ix=24,c1=128,c2=128,c3=16"; @@ -5710,8 +5673,8 @@ static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per p ////////////////////////////// // 2D Lissajous // ////////////////////////////// -uint16_t mode_2DLissajous(void) { // By: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DLissajous(void) { // By: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5730,8 +5693,6 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); } SEGMENT.blur(SEGMENT.custom1 >> (1 + SEGMENT.check1 * 3), SEGMENT.check1); - - return FRAMETIME; } // mode_2DLissajous() static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate,Blur,,Speed,Smear;!;!;2;c1=0"; @@ -5739,15 +5700,15 @@ static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,F /////////////////////// // 2D Matrix // /////////////////////// -uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; }; unsigned dataSize = (SEGMENT.length()+7) >> 3; //1 bit per LED for trails - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5801,8 +5762,6 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. bitSet(SEGENV.data[index], bitNum); } } - - return FRAMETIME; } // mode_2Dmatrix() static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Trail,,,Custom color;Spawn,Trail;;2"; @@ -5810,8 +5769,8 @@ static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Tra ///////////////////////// // 2D Metaballs // ///////////////////////// -uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5860,8 +5819,6 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have SEGMENT.setPixelColorXY(x3, y3, WHITE); } } - - return FRAMETIME; } // mode_2Dmetaballs() static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; @@ -5869,8 +5826,8 @@ static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; ////////////////////// // 2D Noise // ////////////////////// -uint16_t mode_2Dnoise(void) { // By Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dnoise(void) { // By Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5883,8 +5840,6 @@ uint16_t mode_2Dnoise(void) { // By Andrew Tuline SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, pixelHue8)); } } - - return FRAMETIME; } // mode_2Dnoise() static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; @@ -5892,8 +5847,8 @@ static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; ////////////////////////////// // 2D Plasma Ball // ////////////////////////////// -uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5920,8 +5875,6 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito } } SEGMENT.blur(SEGMENT.custom2>>5); - - return FRAMETIME; } // mode_2DPlasmaball() static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;;!;2"; @@ -5930,8 +5883,8 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad // 2D Polar Lights // //////////////////////////////// -uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline & @dedehai (palette support) - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline & @dedehai (palette support) + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5955,8 +5908,6 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(palindex, false, false, 255, palbrightness)); } } - - return FRAMETIME; } // mode_2DPolarLights() static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,Flip Palette;;!;2;pal=71"; @@ -5964,8 +5915,8 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale, ///////////////////////// // 2D Pulser // ///////////////////////// -uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -5977,8 +5928,6 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.blur(SEGMENT.intensity>>4); - - return FRAMETIME; } // mode_2DPulser() static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; @@ -5986,8 +5935,8 @@ static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; ///////////////////////// // 2D Sindots // ///////////////////////// -uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6006,8 +5955,6 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom2 >> (3 + SEGMENT.check1), SEGMENT.check1); - - return FRAMETIME; } // mode_2DSindots() static const char _data_FX_MODE_2DSINDOTS[] PROGMEM = "Sindots@!,Dot distance,Fade rate,Blur,,Smear;;!;2;"; @@ -6016,9 +5963,9 @@ static const char _data_FX_MODE_2DSINDOTS[] PROGMEM = "Sindots@!,Dot distance,Fa // 2D Squared Swirl // ////////////////////////////// // custom3 affects the blur amount. -uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://gist.github.com/kriegsman/368b316c55221134b160 +void mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://gist.github.com/kriegsman/368b316c55221134b160 // Modifed by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6039,8 +5986,6 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, strip.now/29, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, strip.now/41, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(k, p, ColorFromPalette(SEGPALETTE, strip.now/73, 255, LINEARBLEND)); - - return FRAMETIME; } // mode_2Dsquaredswirl() static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,Fade,,,Blur;;!;2"; @@ -6048,13 +5993,13 @@ static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,Fade, ////////////////////////////// // 2D Sun Radiation // ////////////////////////////// -uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; - if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) FX_FALLBACK_STATIC; //allocation failed byte *bump = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { @@ -6090,8 +6035,6 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi } yindex += (cols + 2); } - - return FRAMETIME; } // mode_2DSunradiation() static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Variance,Brightness;;;2"; @@ -6099,8 +6042,8 @@ static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Varian ///////////////////////// // 2D Tartan // ///////////////////////// -uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6129,8 +6072,6 @@ uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.so SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); } } - - return FRAMETIME; } // mode_2DTartan() static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,Sharpness;;!;2"; @@ -6138,8 +6079,8 @@ static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,S ///////////////////////// // 2D spaceships // ///////////////////////// -uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6170,8 +6111,6 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht } } SEGMENT.blur(SEGMENT.intensity >> 3, SEGMENT.check1); - - return FRAMETIME; } static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur,,,,Smear;;!;2"; @@ -6181,8 +6120,8 @@ static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur,,,,S ///////////////////////// //// Crazy bees by stepko (c)12.02.21 [https://editor.soulmatelights.com/gallery/651-crazy-bees], adapted by Blaz Kristan (AKA blazoncek), improved by @dedehai #define MAX_BEES 5 -uint16_t mode_2Dcrazybees(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dcrazybees(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6205,7 +6144,7 @@ uint16_t mode_2Dcrazybees(void) { }; } bee_t; - if (!SEGENV.allocateData(sizeof(bee_t)*MAX_BEES)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(bee_t)*MAX_BEES)) FX_FALLBACK_STATIC; //allocation failed bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { @@ -6243,7 +6182,6 @@ uint16_t mode_2Dcrazybees(void) { } } } - return FRAMETIME; } static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur,,,,Smear;;!;2;pal=11,ix=0"; #undef MAX_BEES @@ -6254,8 +6192,8 @@ static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur,,,,Sm ///////////////////////// //// Ghost Rider by stepko (c)2021 [https://editor.soulmatelights.com/gallery/716-ghost-rider], adapted by Blaz Kristan (AKA blazoncek) #define LIGHTERS_AM 64 // max lighters (adequate for 32x32 matrix) -uint16_t mode_2Dghostrider(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dghostrider(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6273,7 +6211,7 @@ uint16_t mode_2Dghostrider(void) { int8_t Vspeed; } lighter_t; - if (!SEGENV.allocateData(sizeof(lighter_t))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(lighter_t))) FX_FALLBACK_STATIC; //allocation failed lighter_t *lighter = reinterpret_cast(SEGENV.data); const size_t maxLighters = min(cols + rows, LIGHTERS_AM); @@ -6332,8 +6270,6 @@ uint16_t mode_2Dghostrider(void) { } SEGMENT.blur(SEGMENT.intensity>>3); } - - return FRAMETIME; } static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate,Blur;;!;2"; #undef LIGHTERS_AM @@ -6343,8 +6279,8 @@ static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate, //////////////////////////// //// Floating Blobs by stepko (c)2021 [https://editor.soulmatelights.com/gallery/573-blobs], adapted by Blaz Kristan (AKA blazoncek) #define MAX_BLOBS 8 -uint16_t mode_2Dfloatingblobs(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dfloatingblobs(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6359,7 +6295,7 @@ uint16_t mode_2Dfloatingblobs(void) { size_t Amount = (SEGMENT.intensity>>5) + 1; // NOTE: be sure to update MAX_BLOBS if you change this - if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(blob_t))) FX_FALLBACK_STATIC; //allocation failed blob_t *blob = reinterpret_cast(SEGENV.data); if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { @@ -6431,8 +6367,6 @@ uint16_t mode_2Dfloatingblobs(void) { SEGMENT.blur(SEGMENT.custom1>>2); if (SEGENV.step < strip.now) SEGENV.step = strip.now + 2000; // change colors every 2 seconds - - return FRAMETIME; } static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; #undef MAX_BLOBS @@ -6441,8 +6375,8 @@ static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail; //////////////////////////// // 2D Scrolling text // //////////////////////////// -uint16_t mode_2Dscrollingtext(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dscrollingtext(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6582,8 +6516,6 @@ uint16_t mode_2Dscrollingtext(void) { if (xoffset + rotLW < 0) continue; // don't draw characters off-screen SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, rotate); } - - return FRAMETIME; } static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; @@ -6592,8 +6524,8 @@ static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Off // 2D Drift Rose // //////////////////////////// //// Drift Rose by stepko (c)2021 [https://editor.soulmatelights.com/gallery/1369-drift-rose-pattern], adapted by Blaz Kristan (AKA blazoncek) improved by @dedehai -uint16_t mode_2Ddriftrose(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Ddriftrose(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6611,8 +6543,6 @@ uint16_t mode_2Ddriftrose(void) { else SEGMENT.wu_pixel(x, y, ColorFromPalette(SEGPALETTE, i * 10)); } SEGMENT.blur(SEGMENT.intensity >> 4, SEGMENT.check1); - - return FRAMETIME; } static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur,,,,Smear;;!;2;pal=11"; @@ -6620,14 +6550,14 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur,,, // 2D PLASMA ROTOZOOMER // ///////////////////////////// // Plasma Rotozoomer by ldirko (c)2020 [https://editor.soulmatelights.com/gallery/457-plasma-rotozoomer], adapted for WLED by Blaz Kristan (AKA blazoncek) -uint16_t mode_2Dplasmarotozoom() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dplasmarotozoom() { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; unsigned dataSize = SEGMENT.length() + sizeof(float); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed float *a = reinterpret_cast(SEGENV.data); byte *plasma = reinterpret_cast(SEGENV.data+sizeof(float)); @@ -6657,8 +6587,6 @@ uint16_t mode_2Dplasmarotozoom() { } *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed if(*a < -6283.18530718f) *a += 6283.18530718f; // 1000*2*PI, protect sin/cos from very large input float values (will give wrong results) - - return FRAMETIME; } static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale,,,,Alt;;!;2;pal=54"; @@ -6673,13 +6601,13 @@ static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale ///////////////////////////////// // * Ripple Peak // ///////////////////////////////// -uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuline. +void mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuline. // This currently has no controls. #define MAXSTEPS 16 // Case statement wouldn't allow a variable. unsigned maxRipples = 16; unsigned dataSize = sizeof(Ripple) * maxRipples; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); @@ -6738,8 +6666,6 @@ uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuli break; } // switch step } // for i - - return FRAMETIME; } // mode_ripplepeak() static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Max # of ripples,Select bin,Volume (min);!,!;!;1v;c2=0,m12=0,si=0"; // Pixel, Beatsin @@ -6749,8 +6675,8 @@ static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Ma // * 2D Swirl // ///////////////////////// // By: Mark Kriegsman https://gist.github.com/kriegsman/5adca44e14ad025e6d3b , modified by Andrew Tuline -uint16_t mode_2DSwirl(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DSwirl(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6778,8 +6704,6 @@ uint16_t mode_2DSwirl(void) { SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (strip.now / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (strip.now / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (strip.now / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); - - return FRAMETIME; } // mode_2DSwirl() static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,Bg Swirl;!;2v;ix=64,si=0"; // Beatsin // TODO: color 1 unused? @@ -6788,8 +6712,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B // * 2D Waverly // ///////////////////////// // By: Stepko, https://editor.soulmatelights.com/gallery/652-wave , modified by Andrew Tuline -uint16_t mode_2DWaverly(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DWaverly(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -6815,8 +6739,6 @@ uint16_t mode_2DWaverly(void) { } } if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100); - - return FRAMETIME; } // mode_2DWaverly() static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity,,,,,Blur;;!;2v;ix=64,si=0"; // Beatsin @@ -6834,11 +6756,11 @@ typedef struct Gravity { // Gravcenter effects By Andrew Tuline. // Gravcenter base function for Gravcenter (0), Gravcentric (1), Gravimeter (2), Gravfreq (3) (merged by @dedehai) -uint16_t mode_gravcenter_base(unsigned mode) { - if (SEGLEN == 1) return mode_static(); +void mode_gravcenter_base(unsigned mode) { + if (SEGLEN == 1) FX_FALLBACK_STATIC; const unsigned dataSize = sizeof(gravity); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); @@ -6915,20 +6837,18 @@ uint16_t mode_gravcenter_base(unsigned mode) { } } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; - - return FRAMETIME; } -uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. - return mode_gravcenter_base(0); +void mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. + mode_gravcenter_base(0); } static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin /////////////////////// // * GRAVCENTRIC // /////////////////////// -uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. - return mode_gravcenter_base(1); +void mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. + mode_gravcenter_base(1); } static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=3,si=0"; // Corner, Beatsin @@ -6936,8 +6856,8 @@ static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fal /////////////////////// // * GRAVIMETER // /////////////////////// -uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. - return mode_gravcenter_base(2); +void mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. + mode_gravcenter_base(2); } static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin @@ -6945,8 +6865,8 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall, /////////////////////// // ** Gravfreq // /////////////////////// -uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. - return mode_gravcenter_base(3); +void mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. + mode_gravcenter_base(3); } static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensitivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin @@ -6954,7 +6874,7 @@ static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sens ////////////////////// // * JUGGLES // ////////////////////// -uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. +void mode_juggles(void) { // Juggles. By Andrew Tuline. um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; @@ -6965,8 +6885,6 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. // if SEGLEN equals 1, we will always set color to the first and only pixel, but the effect is still good looking SEGMENT.setPixelColor(beatsin16_t(SEGMENT.speed/4+i*2,0,SEGLEN-1), color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now/4+i*2, false, PALETTE_SOLID_WRAP, 0), my_sampleAgc)); } - - return FRAMETIME; } // mode_juggles() static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;01v;m12=0,si=0"; // Pixels, Beatsin @@ -6974,10 +6892,10 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!; ////////////////////// // * MATRIPIX // ////////////////////// -uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. +void mode_matripix(void) { // Matripix. By Andrew Tuline. // effect can work on single pixels, we just lose the shifting effect unsigned dataSize = sizeof(uint32_t) * SEGLEN; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); @@ -7001,8 +6919,6 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri); SEGMENT.setPixelColor(k, pixels[k]); } - - return FRAMETIME; } // mode_matripix() static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!;!;1v;ix=64,m12=2,si=1"; //,rev=1,mi=1,rY=1,mY=1 Circle, WeWillRockYou, reverseX @@ -7010,8 +6926,8 @@ static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!; ////////////////////// // * MIDNOISE // ////////////////////// -uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. - if (SEGLEN <= 1) return mode_static(); +void mode_midnoise(void) { // Midnoise. By Andrew Tuline. + if (SEGLEN <= 1) FX_FALLBACK_STATIC; // Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. um_data_t *um_data = getAudioData(); @@ -7033,8 +6949,6 @@ uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. SEGENV.aux0=SEGENV.aux0+beatsin8_t(5,0,10); SEGENV.aux1=SEGENV.aux1+beatsin8_t(4,0,10); - - return FRAMETIME; } // mode_midnoise() static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. length;!,!;!;1v;ix=128,m12=1,si=0"; // Bar, Beatsin @@ -7043,7 +6957,7 @@ static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. le // * NOISEFIRE // ////////////////////// // I am the god of hellfire. . . Volume (only) reactive fire routine. Oh, look how short this is. -uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. +void mode_noisefire(void) { // Noisefire. By Andrew Tuline. CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,2), CHSV(0,255,4), CHSV(0,255,8), CHSV(0, 255, 8), // Fire palette definition. Lower value = darker. CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, @@ -7061,8 +6975,6 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. SEGMENT.setPixelColor(i, ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND)); // Use my own palette. } - - return FRAMETIME; } // mode_noisefire() static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2,si=0"; // Circle, Beatsin @@ -7070,7 +6982,7 @@ static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2 /////////////////////// // * Noisemeter // /////////////////////// -uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. +void mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; @@ -7092,8 +7004,6 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. SEGENV.aux0+=beatsin8_t(5,0,10); SEGENV.aux1+=beatsin8_t(4,0,10); - - return FRAMETIME; } // mode_noisemeter() static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Width;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin @@ -7101,8 +7011,8 @@ static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Wid ////////////////////// // * PIXELWAVE // ////////////////////// -uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. - if (SEGLEN <= 1) return mode_static(); +void mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. + if (SEGLEN <= 1) FX_FALLBACK_STATIC; // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment if (SEGENV.call == 0) { @@ -7122,8 +7032,6 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. for (unsigned i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left for (unsigned i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } - - return FRAMETIME; } // mode_pixelwave() static const char _data_FX_MODE_PIXELWAVE[] PROGMEM = "Pixelwave@!,Sensitivity;!,!;!;1v;ix=64,m12=2,si=0"; // Circle, Beatsin @@ -7136,9 +7044,9 @@ typedef struct Plasphase { int16_t thatphase; } plasphase; -uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. +void mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment - if (!SEGENV.allocateData(sizeof(plasphase))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(plasphase))) FX_FALLBACK_STATIC; //allocation failed Plasphase* plasmoip = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); @@ -7159,8 +7067,6 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0), thisbright)); } - - return FRAMETIME; } // mode_plasmoid() static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;01v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin @@ -7169,8 +7075,8 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels // * PUDDLES // ////////////////////// // Puddles/Puddlepeak By Andrew Tuline. Merged by @dedehai -uint16_t mode_puddles_base(bool peakdetect) { - if (SEGLEN <= 1) return mode_static(); +void mode_puddles_base(bool peakdetect) { + if (SEGLEN <= 1) FX_FALLBACK_STATIC; unsigned size = 0; uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); unsigned pos = hw_random16(SEGLEN); // Set a random starting position. @@ -7201,17 +7107,15 @@ uint16_t mode_puddles_base(bool peakdetect) { for (unsigned i=0; i 210 results in a alternating pattern, this could be fixed by mapping but some may like it (very old bug) } - - return FRAMETIME; } // mode_blurz() static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color mix;!;1f;m12=0,si=0"; // Pixels, Beatsin @@ -7280,7 +7180,7 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color ///////////////////////// // ** DJLight // ///////////////////////// -uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. +void mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. // No need to prevent from executing on single led strips, only mid will be set (mid = 0) const int mid = SEGLEN / 2; @@ -7302,8 +7202,6 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); // move to the left for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } - - return FRAMETIME; } // mode_DJLight() static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2,si=0"; // Circle, Beatsin @@ -7311,8 +7209,8 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2, //////////////////// // ** Freqmap // //////////////////// -uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. - if (SEGLEN <= 1) return mode_static(); +void mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. + if (SEGLEN <= 1) FX_FALLBACK_STATIC; // Start frequency = 60 Hz and log10(60) = 1.78 // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 @@ -7335,8 +7233,6 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. uint8_t bright = (uint8_t)my_magnitude; SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), bright)); - - return FRAMETIME; } // mode_freqmap() static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting color;!,!;!;1f;m12=0,si=0"; // Pixels, Beatsin @@ -7344,7 +7240,7 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting /////////////////////// // ** Freqmatrix // /////////////////////// -uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. +void mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. // No need to prevent from executing on single led strips, we simply change pixel 0 each time and avoid the shift um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[4]; @@ -7387,8 +7283,6 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch // if SEGLEN equals 1 this loop won't execute for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left } - - return FRAMETIME; } // mode_freqmatrix() static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;01f;m12=3,si=0"; // Corner, Beatsin @@ -7400,7 +7294,7 @@ static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound e // End frequency = 5120 Hz and lo10(5120) = 3.71 // SEGMENT.speed select faderate // SEGMENT.intensity select colour index -uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. +void mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[4]; float my_magnitude = *(float*)um_data->u_data[5] / 16.0f; @@ -7421,8 +7315,6 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. unsigned locn = hw_random16(0,SEGLEN); SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (uint8_t)my_magnitude)); } - - return FRAMETIME; } // mode_freqpixels() static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;!,!,;!;1f;m12=0,si=0"; // Pixels, Beatsin @@ -7442,7 +7334,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // // As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. // Depending on the music stream you have you might find it useful to change the frequency mapping. -uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. +void mode_freqwave(void) { // Freqwave. By Andreas Pleschung. // As before, this effect can also work on single pixels, we just lose the shifting effect um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[4]; @@ -7484,8 +7376,6 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun for (unsigned i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left for (unsigned i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } - - return FRAMETIME; } // mode_freqwave() static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;01f;m12=2,si=0"; // Circle, Beatsin @@ -7493,7 +7383,7 @@ static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effec ////////////////////// // ** Noisemove // ////////////////////// -uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline +void mode_noisemove(void) { // Noisemove. By: Andrew Tuline um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; @@ -7507,8 +7397,6 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli locn = map(locn, 7500, 58000, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*64, false, PALETTE_SOLID_WRAP, 0), uint8_t(fftResult[i % 16]*4))); } - - return FRAMETIME; } // mode_noisemove() static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Move speed,Fade rate;!,!;!;01f;m12=0,si=0"; // Pixels, Beatsin @@ -7516,7 +7404,7 @@ static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Move speed,Fade ////////////////////// // ** Rocktaves // ////////////////////// -uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline +void mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*) um_data->u_data[4]; float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; @@ -7542,8 +7430,6 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac unsigned i = map(beatsin8_t(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); i = constrain(i, 0U, SEGLEN-1U); SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); - - return FRAMETIME; } // mode_rocktaves() static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=1,si=0"; // Bar, Beatsin @@ -7552,10 +7438,10 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12= // ** Waterfall // /////////////////////// // Combines peak detection with FFT_MajorPeak and FFT_Magnitude. -uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline +void mode_waterfall(void) { // Waterfall. By: Andrew Tuline // effect can work on single pixels, we just lose the shifting effect unsigned dataSize = sizeof(uint32_t) * SEGLEN; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); um_data_t *um_data = getAudioData(); @@ -7598,8 +7484,6 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin SEGMENT.setPixelColor(i, pixels[i]); } } - - return FRAMETIME; } // mode_waterfall() static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color,Select bin,Volume (min);!,!;!;01f;c2=0,m12=2,si=0"; // Circles, Beatsin @@ -7608,15 +7492,15 @@ static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color, ///////////////////////// // ** 2D GEQ // ///////////////////////// -uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int NUM_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); const int CENTER_BIN = map(SEGMENT.custom3, 0, 31, 0, 15); const int cols = SEG_W; const int rows = SEG_H; - if (!SEGENV.allocateData(cols*sizeof(uint16_t))) return mode_static(); //allocation failed + if (!SEGENV.allocateData(cols*sizeof(uint16_t))) FX_FALLBACK_STATIC; //allocation failed uint16_t *previousBarHeight = reinterpret_cast(SEGENV.data); //array of previous bar heights per frequency band um_data_t *um_data = getAudioData(); @@ -7660,8 +7544,6 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect } - - return FRAMETIME; } // mode_2DGEQ() static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,Bin,Color bars;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0,c3=0"; @@ -7669,8 +7551,8 @@ static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# ///////////////////////// // ** 2D Funky plank // ///////////////////////// -uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -7713,8 +7595,6 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil } } } - - return FRAMETIME; } // mode_2DFunkyPlank static const char _data_FX_MODE_2DFUNKYPLANK[] PROGMEM = "Funky Plank@Scroll speed,,# of bands;;;2f;si=0"; // Beatsin @@ -7757,8 +7637,8 @@ static uint8_t akemi[] PROGMEM = { 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -uint16_t mode_2DAkemi(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2DAkemi(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -7817,8 +7697,6 @@ uint16_t mode_2DAkemi(void) { } } } - - return FRAMETIME; } // mode_2DAkemi static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette;2f;si=0"; //beatsin @@ -7826,8 +7704,8 @@ static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Hea // Distortion waves - ldirko // https://editor.soulmatelights.com/gallery/1089-distorsion-waves // adapted for WLED by @blazoncek, improvements by @dedehai -uint16_t mode_2Ddistortionwaves() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Ddistortionwaves() { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -7898,8 +7776,6 @@ uint16_t mode_2Ddistortionwaves() { // palette mode and not filling: smear-blur to cover up palette wrapping artefacts if(!SEGMENT.check1 && SEGMENT.palette) SEGMENT.blur(200, true); - - return FRAMETIME; } static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale,,,,Fill,Zoom,Alt;;!;2;pal=0"; @@ -7960,8 +7836,8 @@ static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) { } } -uint16_t mode_2Dsoap() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dsoap() { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -7969,7 +7845,7 @@ uint16_t mode_2Dsoap() { const size_t segSize = SEGMENT.width() * SEGMENT.height(); // prevent reallocation if mirrored or grouped const size_t dataSize = segSize * (sizeof(uint8_t) + sizeof(CRGB)); // pixels and noise - if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) FX_FALLBACK_STATIC; //allocation failed uint8_t *noise3d = reinterpret_cast(SEGENV.data); CRGB *pixels = reinterpret_cast(SEGENV.data + segSize * sizeof(uint8_t)); @@ -8003,8 +7879,6 @@ uint16_t mode_2Dsoap() { soapPixels(true, noise3d, pixels); // rows soapPixels(false, noise3d, pixels); // cols - - return FRAMETIME; } static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness,Density;;!;2;pal=11"; @@ -8013,8 +7887,8 @@ static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness,Density;;! //Octopus (https://editor.soulmatelights.com/gallery/671-octopus) //Stepko and Sutaburosu // adapted for WLED by @blazoncek -uint16_t mode_2Doctopus() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Doctopus() { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -8027,7 +7901,7 @@ uint16_t mode_2Doctopus() { } map_t; const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(map_t); // prevent reallocation if mirrored or grouped - if (!SEGENV.allocateData(dataSize + 2)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize + 2)) FX_FALLBACK_STATIC; //allocation failed map_t *rMap = reinterpret_cast(SEGENV.data); uint8_t *offsX = reinterpret_cast(SEGENV.data + dataSize); @@ -8063,7 +7937,6 @@ uint16_t mode_2Doctopus() { SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity)); } } - return FRAMETIME; } static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs,fasttan;;!;2;"; @@ -8071,8 +7944,8 @@ static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offse //Waving Cell //@Stepko (https://editor.soulmatelights.com/gallery/1704-wavingcells) // adapted for WLED by @blazoncek, improvements by @dedehai -uint16_t mode_2Dwavingcell() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up +void mode_2Dwavingcell() { + if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up const int cols = SEG_W; const int rows = SEG_H; @@ -8089,7 +7962,6 @@ uint16_t mode_2Dwavingcell() { } } SEGMENT.blur(SEGMENT.intensity); - return FRAMETIME; } static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,Blur,Amplitude 1,Amplitude 2,Amplitude 3,,Flow;;!;2;ix=0"; @@ -8102,15 +7974,15 @@ static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,Blur,Amp by DedeHai (Damian Schneider) */ #define NUMBEROFSOURCES 8 -uint16_t mode_particlevortex(void) { +void mode_particlevortex(void) { if (SEGLEN == 1) - return mode_static(); + FX_FALLBACK_STATIC; ParticleSystem2D *PartSys = nullptr; uint32_t i, j; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) - return mode_static(); // allocation failed + FX_FALLBACK_STATIC; // allocation failed #ifdef ESP8266 PartSys->setMotionBlur(180); #else @@ -8128,7 +8000,7 @@ uint16_t mode_particlevortex(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) uint32_t spraycount = min(PartSys->numSources, (uint32_t)(1 + (SEGMENT.custom1 >> 5))); // number of sprays to display, 1-8 @@ -8205,7 +8077,6 @@ uint16_t mode_particlevortex(void) { } } PartSys->update(); //update all particles and render to frame - return FRAMETIME; } #undef NUMBEROFSOURCES static const char _data_FX_MODE_PARTICLEVORTEX[] PROGMEM = "PS Vortex@Rotation Speed,Particle Speed,Arms,Flip,Nozzle,Smear,Direction,Random Flip;;!;2;pal=27,c1=200,c2=0,c3=0"; @@ -8217,13 +8088,13 @@ static const char _data_FX_MODE_PARTICLEVORTEX[] PROGMEM = "PS Vortex@Rotation S */ #define NUMBEROFSOURCES 8 -uint16_t mode_particlefireworks(void) { +void mode_particlefireworks(void) { ParticleSystem2D *PartSys = nullptr; uint32_t numRockets; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) - return mode_static(); // allocation failed + FX_FALLBACK_STATIC; // allocation failed PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) PartSys->setWallHardness(120); // ground bounce is fixed @@ -8237,7 +8108,7 @@ uint16_t mode_particlefireworks(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) numRockets = map(SEGMENT.speed, 0 , 255, 4, min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES)); @@ -8349,7 +8220,6 @@ uint16_t mode_particlefireworks(void) { } } PartSys->update(); // update and render - return FRAMETIME; } #undef NUMBEROFSOURCES static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "PS Fireworks@Launches,Explosion Size,Fuse,Blur,Gravity,Cylinder,Ground,Fast;;!;2;pal=11,ix=50,c1=40,c2=0,c3=12"; @@ -8361,7 +8231,7 @@ static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "PS Fireworks@Laun by DedeHai (Damian Schneider) */ #define NUMBEROFSOURCES 1 -uint16_t mode_particlevolcano(void) { +void mode_particlevolcano(void) { ParticleSystem2D *PartSys = nullptr; PSsettings2D volcanosettings; volcanosettings.asByte = 0b00000100; // PS settings for volcano movement: bounceX is enabled @@ -8370,7 +8240,7 @@ uint16_t mode_particlevolcano(void) { if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, no additional data needed - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setBounceY(true); PartSys->setGravity(); // enable with default gforce @@ -8391,7 +8261,7 @@ uint16_t mode_particlevolcano(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); // number of volcanoes @@ -8423,7 +8293,6 @@ uint16_t mode_particlevolcano(void) { PartSys->enableParticleCollisions(false); PartSys->update(); // update and render - return FRAMETIME; } #undef NUMBEROFSOURCES static const char _data_FX_MODE_PARTICLEVOLCANO[] PROGMEM = "PS Volcano@Speed,Intensity,Move,Bounce,Spread,AgeColor,Walls,Collide;;!;2;pal=35,sx=100,ix=190,c1=0,c2=160,c3=6,o1=1"; @@ -8433,21 +8302,21 @@ static const char _data_FX_MODE_PARTICLEVOLCANO[] PROGMEM = "PS Volcano@Speed,In realistic fire effect using particles. heat based and using perlin-noise for wind by DedeHai (Damian Schneider) */ -uint16_t mode_particlefire(void) { +void mode_particlefire(void) { ParticleSystem2D *PartSys = nullptr; uint32_t i; // index variable uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, SEGMENT.vWidth(), 4)) //maximum number of source (PS may limit based on segment size); need 4 additional bytes for time keeping (uint32_t lastcall) - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D SEGENV.aux0 = hw_random16(); // aux0 is wind position (index) in the perlin noise } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setWrapX(SEGMENT.check2); @@ -8460,9 +8329,7 @@ uint16_t mode_particlefire(void) { uint32_t period = strip.now - *lastcall; if (period < (uint32_t)map(SEGMENT.speed, 0, 99, 50, 10)) { // limit to 90FPS - 20FPS SEGMENT.call--; //skipping a frame, decrement the counter (on call0, this is never executed as lastcall is 0, so its fine to not check if >0) - //still need to render the frame or flickering will occur in transitions - PartSys->updateFire(SEGMENT.intensity, true); // render the fire without updating particles (render only) - return FRAMETIME; //do not update this frame + return; //do not update this frame } *lastcall = strip.now; } @@ -8528,9 +8395,7 @@ uint16_t mode_particlefire(void) { PartSys->flameEmit(PartSys->sources[j]); } - PartSys->updateFire(SEGMENT.intensity, false); // update and render the fire - - return FRAMETIME; + PartSys->updateFire(SEGMENT.intensity); // update and render the fire } static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensity,Flame Height,Wind,Spread,Smooth,Cylinder,Turbulence;;!;2;pal=35,sx=110,c1=110,c2=50,c3=31,o1=1"; @@ -8541,12 +8406,12 @@ static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensit Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particlepit(void) { +void mode_particlepit(void) { ParticleSystem2D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 0, 0, true, false)) // init - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); PartSys->setGravity(); // enable with default gravity PartSys->setUsedParticles(170); // use 75% of available particles @@ -8554,7 +8419,7 @@ uint16_t mode_particlepit(void) { else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -8585,7 +8450,6 @@ uint16_t mode_particlepit(void) { PartSys->perParticleSize = true; PartSys->advPartProps[i].size = hw_random16(SEGMENT.custom1); // set each particle to random size } else { - PartSys->perParticleSize = false; PartSys->setParticleSize(SEGMENT.custom1); // set global size PartSys->advPartProps[i].size = SEGMENT.custom1; // also set individual size for consistency } @@ -8602,8 +8466,6 @@ uint16_t mode_particlepit(void) { PartSys->applyFriction(frictioncoefficient); PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEPIT[] PROGMEM = "PS Ballpit@Speed,Intensity,Size,Hardness,Saturation,Cylinder,Walls,Ground;;!;2;pal=11,sx=100,ix=220,c1=70,c2=180,c3=31,o3=1"; @@ -8612,14 +8474,14 @@ static const char _data_FX_MODE_PARTICLEPIT[] PROGMEM = "PS Ballpit@Speed,Intens Uses palette for particle color, spray source at top emitting particles, many config options by DedeHai (Damian Schneider) */ -uint16_t mode_particlewaterfall(void) { +void mode_particlewaterfall(void) { ParticleSystem2D *PartSys = nullptr; uint8_t numSprays; uint32_t i = 0; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 12)) // init, request 12 sources, no additional data needed - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setGravity(); // enable with default gforce PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) @@ -8640,7 +8502,7 @@ uint16_t mode_particlewaterfall(void) { else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -8675,7 +8537,6 @@ uint16_t mode_particlewaterfall(void) { PartSys->applyFriction(1); // add just a tiny amount of friction to help smooth things PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEWATERFALL[] PROGMEM = "PS Waterfall@Speed,Intensity,Variation,Collide,Position,Cylinder,Walls,Ground;;!;2;pal=9,sx=15,ix=200,c1=32,c2=160,o3=1"; @@ -8684,13 +8545,13 @@ static const char _data_FX_MODE_PARTICLEWATERFALL[] PROGMEM = "PS Waterfall@Spee Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particlebox(void) { +void mode_particlebox(void) { ParticleSystem2D *PartSys = nullptr; uint32_t i; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1, 0, true)) // init - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setBounceX(true); PartSys->setBounceY(true); SEGENV.aux0 = hw_random16(); // position in perlin noise @@ -8699,7 +8560,7 @@ uint16_t mode_particlebox(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)200)); // wall hardness is 200 or more @@ -8762,8 +8623,6 @@ uint16_t mode_particlebox(void) { PartSys->applyFriction(1); PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "PS Box@!,Particles,Tilt,Hardness,Size,Random,Washing Machine,Sloshing;;!;2;pal=53,ix=50,c3=1,o1=1"; @@ -8772,13 +8631,13 @@ static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "PS Box@!,Particles,Tilt calculates slope gradient at the particle positions and applies 'downhill' force, resulting in a fuzzy perlin noise display by DedeHai (Damian Schneider) */ -uint16_t mode_particleperlin(void) { +void mode_particleperlin(void) { ParticleSystem2D *PartSys = nullptr; uint32_t i; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1, 0, true)) // init with 1 source and advanced properties - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); // should never happen, but lets make sure there are no stray particles PartSys->setMotionBlur(230); // anable motion blur @@ -8789,7 +8648,7 @@ uint16_t mode_particleperlin(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setWrapX(SEGMENT.check1); @@ -8825,7 +8684,6 @@ uint16_t mode_particleperlin(void) { PartSys->applyFriction(2); PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o3=1"; @@ -8834,7 +8692,7 @@ static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed by DedeHai (Damian Schneider) */ #define NUMBEROFSOURCES 8 -uint16_t mode_particleimpact(void) { +void mode_particleimpact(void) { ParticleSystem2D *PartSys = nullptr; uint32_t numMeteors; PSsettings2D meteorsettings; @@ -8842,7 +8700,7 @@ uint16_t mode_particleimpact(void) { if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, no additional data needed - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); PartSys->setGravity(); // enable default gravity PartSys->setBounceY(true); // always use ground bounce @@ -8857,7 +8715,7 @@ uint16_t mode_particleimpact(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -8932,7 +8790,6 @@ uint16_t mode_particleimpact(void) { } PartSys->update(); // update and render - return FRAMETIME; } #undef NUMBEROFSOURCES static const char _data_FX_MODE_PARTICLEIMPACT[] PROGMEM = "PS Impact@Launches,!,Force,Hardness,Blur,Cylinder,Walls,Collide;;!;2;pal=0,sx=32,ix=85,c1=70,c2=130,c3=0,o3=1"; @@ -8943,7 +8800,7 @@ static const char _data_FX_MODE_PARTICLEIMPACT[] PROGMEM = "PS Impact@Launches,! Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleattractor(void) { +void mode_particleattractor(void) { ParticleSystem2D *PartSys = nullptr; PSsettings2D sourcesettings; sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) @@ -8952,7 +8809,7 @@ uint16_t mode_particleattractor(void) { PSparticle *attractor; // particle pointer to the attractor if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1, sizeof(PSparticle), true)) // init using 1 source and advanced particle settings - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->sources[0].source.hue = hw_random16(); PartSys->sources[0].source.vx = -7; // will collied with wall and get random bounce direction PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide @@ -8973,7 +8830,7 @@ uint16_t mode_particleattractor(void) { } if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9025,7 +8882,6 @@ uint16_t mode_particleattractor(void) { PartSys->applyFriction(2); PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags, &sourcesettings); // move the source PartSys->update(); // update and render - return FRAMETIME; } //static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Size,Collide,Friction,AgeColor,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=1,c2=0"; static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Size,Collide,Friction,AgeColor,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=2,c2=0"; @@ -9035,13 +8891,13 @@ static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particlespray(void) { +void mode_particlespray(void) { ParticleSystem2D *PartSys = nullptr; const uint8_t hardness = 200; // collision hardness is fixed if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1)) // init, no additional data needed - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) PartSys->setBounceY(true); PartSys->setMotionBlur(200); // anable motion blur @@ -9054,7 +8910,7 @@ uint16_t mode_particlespray(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9099,7 +8955,6 @@ uint16_t mode_particlespray(void) { } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLESPRAY[] PROGMEM = "PS Spray@Speed,!,Left/Right,Up/Down,Angle,Gravity,Cylinder/Square,Collide;;!;2v;pal=0,sx=150,ix=150,c1=220,c2=30,c3=21"; @@ -9109,19 +8964,19 @@ static const char _data_FX_MODE_PARTICLESPRAY[] PROGMEM = "PS Spray@Speed,!,Left Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleGEQ(void) { +void mode_particleGEQ(void) { ParticleSystem2D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1)) - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); PartSys->setUsedParticles(170); // use 2/3 of available particles } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! uint32_t i; // set particle system properties @@ -9172,7 +9027,6 @@ uint16_t mode_particleGEQ(void) { } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEGEQ[] PROGMEM = "PS GEQ 2D@Speed,Intensity,Diverge,Bounce,Gravity,Cylinder,Walls,Floor;;!;2f;pal=0,sx=155,ix=200,c1=0"; @@ -9184,14 +9038,14 @@ static const char _data_FX_MODE_PARTICLEGEQ[] PROGMEM = "PS GEQ 2D@Speed,Intensi by DedeHai (Damian Schneider) */ #define NUMBEROFSOURCES 16 -uint16_t mode_particlecenterGEQ(void) { +void mode_particlecenterGEQ(void) { ParticleSystem2D *PartSys = nullptr; uint8_t numSprays; uint32_t i; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, request 16 sources - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); for (i = 0; i < numSprays; i++) { @@ -9207,7 +9061,7 @@ uint16_t mode_particlecenterGEQ(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); @@ -9245,7 +9099,6 @@ uint16_t mode_particlecenterGEQ(void) { j = (j + 1) % numSprays; } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLECIRCULARGEQ[] PROGMEM = "PS GEQ Nova@Speed,Intensity,Rotation Speed,Color Change,Nozzle,,Direction;;!;2f;pal=13,ix=180,c1=0,c2=0,c3=8"; @@ -9253,14 +9106,14 @@ static const char _data_FX_MODE_PARTICLECIRCULARGEQ[] PROGMEM = "PS GEQ Nova@Spe Particle replacement of Ghost Rider by DedeHai (Damian Schneider), original FX by stepko adapted by Blaz Kristan (AKA blazoncek) */ #define MAXANGLESTEP 2200 //32767 means 180° -uint16_t mode_particleghostrider(void) { +void mode_particleghostrider(void) { ParticleSystem2D *PartSys = nullptr; PSsettings2D ghostsettings; ghostsettings.asByte = 0b0000011; //enable wrapX and wrapY if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1)) // init, no additional data needed - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) PartSys->sources[0].maxLife = 260; // lifetime in frames PartSys->sources[0].minLife = 250; @@ -9273,7 +9126,7 @@ uint16_t mode_particleghostrider(void) { } if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! if (SEGMENT.intensity > 0) { // spiraling if (SEGENV.aux1) { @@ -9325,7 +9178,6 @@ uint16_t mode_particleghostrider(void) { PartSys->sources[0].source.hue += (SEGMENT.custom2 - 190) >> 2; PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEGHOSTRIDER[] PROGMEM = "PS Ghost Rider@Speed,Spiral,Blur,Color Cycle,Spread,AgeColor,Walls;;!;2;pal=1,sx=70,ix=0,c1=220,c2=30,c3=21,o1=1"; @@ -9334,12 +9186,12 @@ static const char _data_FX_MODE_PARTICLEGHOSTRIDER[] PROGMEM = "PS Ghost Rider@S Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleblobs(void) { +void mode_particleblobs(void) { ParticleSystem2D *PartSys = nullptr; if (SEGMENT.call == 0) { if (!initParticleSystem2D(PartSys, 0, 0, true, true)) //init, no additional bytes, advanced size & size control - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->setBounceX(true); PartSys->setBounceY(true); PartSys->setWallHardness(255); @@ -9351,7 +9203,7 @@ uint16_t mode_particleblobs(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 25, 128)); // minimum 10%, maximum 50% of available particles (note: PS ensures at least 1) @@ -9399,8 +9251,6 @@ uint16_t mode_particleblobs(void) { PartSys->setMotionBlur(((SEGMENT.custom3) << 3) + 7); PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs,Size,Life,Blur,Wobble,Collide,Pulsate;;!;2v;sx=30,ix=64,c1=200,c2=130,c3=0,o3=1"; @@ -9409,13 +9259,13 @@ static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs, Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particlegalaxy(void) { +void mode_particlegalaxy(void) { ParticleSystem2D *PartSys = nullptr; PSsettings2D sourcesettings; sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1, 0, true)) // init using 1 source and advanced particle settings - return mode_static(); // allocation failed or not 2D + FX_FALLBACK_STATIC; // allocation failed or not 2D PartSys->sources[0].source.vx = -4; // will collide with wall and get random bounce direction PartSys->sources[0].source.x = PartSys->maxX >> 1; // start in the center PartSys->sources[0].source.y = PartSys->maxY >> 1; @@ -9430,7 +9280,7 @@ uint16_t mode_particlegalaxy(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS } if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) uint8_t particlesize = SEGMENT.custom1; @@ -9499,7 +9349,6 @@ uint16_t mode_particlegalaxy(void) { } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEGALAXY[] PROGMEM = "PS Galaxy@!,!,Size,,Color,,Starfield,Trace;;!;2;pal=59,sx=80,c1=1,c3=4"; @@ -9516,12 +9365,12 @@ static const char _data_FX_MODE_PARTICLEGALAXY[] PROGMEM = "PS Galaxy@!,!,Size,, Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleDrip(void) { +void mode_particleDrip(void) { ParticleSystem1D *PartSys = nullptr; //uint8_t numSprays; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 4)) // init - return mode_static(); // allocation failed or single pixel + FX_FALLBACK_STATIC; // allocation failed or single pixel PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) PartSys->sources[0].source.hue = hw_random16(); SEGENV.aux1 = 0xFFFF; // invalidate @@ -9530,7 +9379,7 @@ uint16_t mode_particleDrip(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9610,7 +9459,6 @@ uint16_t mode_particleDrip(void) { } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEDRIP[] PROGMEM = "PS DripDrop@Speed,!,Splash,Blur,Gravity,Rain,PushSplash,Smooth;,!;!;1;pal=0,sx=150,ix=25,c1=220,c2=30,c3=21"; @@ -9621,12 +9469,12 @@ static const char _data_FX_MODE_PARTICLEDRIP[] PROGMEM = "PS DripDrop@Speed,!,Sp Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particlePinball(void) { +void mode_particlePinball(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide (if enabled) PartSys->sources[0].source.x = -1000; // shoot up from below //PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) @@ -9637,7 +9485,7 @@ uint16_t mode_particlePinball(void) { PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings //uint32_t hardness = 240 + (SEGMENT.custom1>>4); @@ -9727,7 +9575,6 @@ uint16_t mode_particlePinball(void) { //} PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PSPINBALL[] PROGMEM = "PS Pinball@Speed,!,Size,Blur,Gravity,Collide,Rolling,Position Color;,!;!;1;pal=0,ix=220,c2=0,c3=8,o1=1"; @@ -9740,12 +9587,12 @@ static const char _data_FX_MODE_PSPINBALL[] PROGMEM = "PS Pinball@Speed,!,Size,B Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleDancingShadows(void) { +void mode_particleDancingShadows(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1)) // init, one source - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->sources[0].maxLife = 1000; //set long life (kill out of bounds is done in custom way) PartSys->sources[0].minLife = PartSys->sources[0].maxLife; } @@ -9754,7 +9601,7 @@ uint16_t mode_particleDancingShadows(void) { } if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9846,8 +9693,6 @@ uint16_t mode_particleDancingShadows(void) { } PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PARTICLEDANCINGSHADOWS[] PROGMEM = "PS Dancing Shadows@Speed,!,Blur,Color Cycle,,Smear,Position Color,Smooth;,!;!;1;sx=100,ix=180,c1=0,c2=0"; @@ -9856,20 +9701,20 @@ static const char _data_FX_MODE_PARTICLEDANCINGSHADOWS[] PROGMEM = "PS Dancing S Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleFireworks1D(void) { +void mode_particleFireworks1D(void) { ParticleSystem1D *PartSys = nullptr; uint8_t *forcecounter; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 4, 150, 4, true)) // init advanced particle system - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); PartSys->sources[0].sourceFlags.custom1 = 1; // set rocket state to standby } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9877,6 +9722,7 @@ uint16_t mode_particleFireworks1D(void) { PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur int32_t gravity = (1 + (SEGMENT.speed >> 3)); // gravity value used for rocket speed calculation PartSys->setGravity(SEGMENT.speed ? gravity : 0); // set gravity + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering (global size, disables per particle size) if (PartSys->sources[0].sourceFlags.custom1 == 1) { // rocket is on standby PartSys->sources[0].source.ttl--; @@ -9898,7 +9744,6 @@ uint16_t mode_particleFireworks1D(void) { PartSys->sources[0].source.vx = min(speed, (uint32_t)127); PartSys->sources[0].source.ttl = 4000; PartSys->sources[0].sat = 30; // low saturation exhaust - PartSys->sources[0].size = SEGMENT.check3; // single or double pixel rendering PartSys->sources[0].sourceFlags.reversegrav = false ; // normal gravity if (SEGENV.aux0) { // inverted rockets launch from end @@ -9967,7 +9812,6 @@ uint16_t mode_particleFireworks1D(void) { if (PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan else PartSys->particles[i].ttl = 0; } - return FRAMETIME; } static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Gravity,Explosion,Firing side,Blur,Color,Colorful,Trail,Smooth;,!;!;1;c2=30,o1=1"; @@ -9976,7 +9820,7 @@ static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Grav Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleSparkler(void) { +void mode_particleSparkler(void) { ParticleSystem1D *PartSys = nullptr; uint32_t numSparklers; PSsettings1D sparklersettings; @@ -9984,11 +9828,11 @@ uint16_t mode_particleSparkler(void) { if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 16, 128 ,0, true)) // init, no additional data needed - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -9999,6 +9843,7 @@ uint16_t mode_particleSparkler(void) { numSparklers = PartSys->numSources; PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur/overlay //PartSys->setSmearBlur(SEGMENT.custom2); // anable smearing blur + PartSys->setParticleSize( SEGMENT.check3 ? 60 : 0); // single pixel or large particle rendering for (uint32_t i = 0; i < numSparklers; i++) { PartSys->sources[i].source.hue = hw_random16(); @@ -10011,7 +9856,6 @@ uint16_t mode_particleSparkler(void) { PartSys->sources[i].source.vx = PartSys->sources[i].source.vx > 0 ? speed : -speed; // update speed, do not change direction PartSys->sources[i].source.ttl = 400; // replenish its life (setting it perpetual uses more code) PartSys->sources[i].sat = SEGMENT.custom1; // color saturation - PartSys->sources[i].size = SEGMENT.check3 ? 120 : 0; if (SEGMENT.speed == 255) // random position at highest speed setting PartSys->sources[i].source.x = hw_random16(PartSys->maxX); else @@ -10038,8 +9882,6 @@ uint16_t mode_particleSparkler(void) { if (PartSys->particles[i].ttl > (64 - (SEGMENT.intensity >> 2))) PartSys->particles[i].ttl -= (64 - (SEGMENT.intensity >> 2)); //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan else PartSys->particles[i].ttl = 0; } - - return FRAMETIME; } static const char _data_FX_MODE_PS_SPARKLER[] PROGMEM = "PS Sparkler@Move,!,Saturation,Blur,Sparklers,Slide,Bounce,Large;,!;!;1;pal=0,sx=255,c1=0,c2=0,c3=6"; @@ -10048,21 +9890,21 @@ static const char _data_FX_MODE_PS_SPARKLER[] PROGMEM = "PS Sparkler@Move,!,Satu Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleHourglass(void) { +void mode_particleHourglass(void) { ParticleSystem1D *PartSys = nullptr; constexpr int positionOffset = PS_P_RADIUS_1D / 2;; // resting position offset bool* direction; uint32_t* settingTracker; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 0, 255, 8, false)) // init - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setBounce(true); PartSys->setWallHardness(100); } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10163,8 +10005,6 @@ uint16_t mode_particleHourglass(void) { SEGENV.aux1--; // countdown PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Interval,!,Color,Blur,Gravity,Colorflip,Start,Fast Reset;,!;!;1;pal=34,sx=5,ix=200,c1=140,c2=80,c3=4,o1=1,o2=1,o3=1"; @@ -10173,12 +10013,12 @@ static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Interval, Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particle1Dspray(void) { +void mode_particle1Dspray(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1)) - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); PartSys->setWallHardness(150); PartSys->setParticleSize(1); @@ -10186,7 +10026,7 @@ uint16_t mode_particle1Dspray(void) { else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10215,8 +10055,6 @@ uint16_t mode_particle1Dspray(void) { PartSys->particleFlags[i].reversegrav = PartSys->sources[0].sourceFlags.reversegrav; // update gravity direction } PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PS_1DSPRAY[] PROGMEM = "PS Spray 1D@Speed(+/-),!,Position,Blur,Gravity(+/-),AgeColor,Bounce,Position Color;,!;!;1;sx=200,ix=220,c1=0,c2=0"; @@ -10225,19 +10063,19 @@ static const char _data_FX_MODE_PS_1DSPRAY[] PROGMEM = "PS Spray 1D@Speed(+/-),! Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleBalance(void) { +void mode_particleBalance(void) { ParticleSystem1D *PartSys = nullptr; uint32_t i; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 128)) // init, no additional data needed, use half of max particles - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setParticleSize(1); } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10297,7 +10135,6 @@ uint16_t mode_particleBalance(void) { } } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PS_BALANCE[] PROGMEM = "PS 1D Balance@!,!,Hardness,Blur,Tilt,Position Color,Wrap,Random;,!;!;1;pal=18,c2=0,c3=4,o1=1"; @@ -10306,11 +10143,11 @@ Particle based Chase effect Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleChase(void) { +void mode_particleChase(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 191, 2, true)) // init - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel SEGENV.aux0 = 0xFFFF; // invalidate *PartSys->PSdataEnd = 1; // huedir *(PartSys->PSdataEnd + 1) = 1; // sizedir @@ -10318,7 +10155,7 @@ uint16_t mode_particleChase(void) { else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setColorByPosition(SEGMENT.check3); @@ -10392,9 +10229,7 @@ uint16_t mode_particleChase(void) { } } - PartSys->setParticleSize(SEGMENT.custom1); // if custom1 == 0 this sets rendering size to one pixel PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@!,Density,Size,Hue,Blur,Playful,,Position Color;,!;!;1;pal=11,sx=50,c2=5,c3=0"; @@ -10403,21 +10238,21 @@ static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@!,Density,Size,Hu Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleStarburst(void) { +void mode_particleStarburst(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 200, 0, true)) // init - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); PartSys->enableParticleCollisions(true, 200); - PartSys->sources[0].source.ttl = 1; // set initial stanby time + PartSys->sources[0].source.ttl = 1; // set initial standby time PartSys->sources[0].sat = 0; // emitted particles start out white } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10428,12 +10263,11 @@ uint16_t mode_particleStarburst(void) { uint32_t explosionsize = 4 + hw_random16(SEGMENT.intensity >> 2); PartSys->sources[0].source.hue = hw_random16(); PartSys->sources[0].var = 10 + (explosionsize << 1); - PartSys->sources[0].minLife = 250; + PartSys->sources[0].minLife = 150; PartSys->sources[0].maxLife = 300; PartSys->sources[0].source.x = hw_random(PartSys->maxX); //random explosion position PartSys->sources[0].source.ttl = 10 + hw_random16(255 - SEGMENT.speed); PartSys->sources[0].size = SEGMENT.custom1; // Fragment size - PartSys->setParticleSize(SEGMENT.custom1); // enable advanced size rendering PartSys->sources[0].sourceFlags.collide = SEGMENT.check3; for (uint32_t e = 0; e < explosionsize; e++) { // emit particles if (SEGMENT.check2) @@ -10444,9 +10278,9 @@ uint16_t mode_particleStarburst(void) { //shrink all particles for (uint32_t i = 0; i < PartSys->usedParticles; i++) { if (PartSys->advPartProps[i].size) - PartSys->advPartProps[i].size--; - if (PartSys->advPartProps[i].sat < 251) - PartSys->advPartProps[i].sat += 1 + (SEGMENT.custom3 >> 2); //note: it should be >> 3, the >> 2 creates overflows resulting in blinking if custom3 > 27, which is a bonus feature + PartSys->advPartProps[i].size --; + if (PartSys->advPartProps[i].sat < 250) + PartSys->advPartProps[i].sat += 2 + (SEGMENT.custom3 >> 3); } if (SEGMENT.call % 5 == 0) { @@ -10454,7 +10288,6 @@ uint16_t mode_particleStarburst(void) { } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PS_STARBURST[] PROGMEM = "PS Starburst@Chance,Fragments,Size,Blur,Cooling,Gravity,Colorful,Push;,!;!;1;pal=52,sx=150,ix=150,c1=120,c2=0,c3=21"; @@ -10463,19 +10296,19 @@ static const char _data_FX_MODE_PS_STARBURST[] PROGMEM = "PS Starburst@Chance,Fr Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particle1DGEQ(void) { +void mode_particle1DGEQ(void) { ParticleSystem1D *PartSys = nullptr; uint32_t numSources; uint32_t i; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 16, 255, 0, true)) // init, no additional data needed - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10527,8 +10360,6 @@ uint16_t mode_particle1DGEQ(void) { //TODO: add color control? PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PS_1D_GEQ[] PROGMEM = "PS GEQ 1D@Speed,!,Size,Blur,,,,;,!;!;1f;pal=0,sx=50,ix=200,c1=0,c2=0,c3=0,o1=1,o2=1"; @@ -10537,19 +10368,19 @@ static const char _data_FX_MODE_PS_1D_GEQ[] PROGMEM = "PS GEQ 1D@Speed,!,Size,Bl Uses palette for particle color by DedeHai (Damian Schneider) */ -uint16_t mode_particleFire1D(void) { +void mode_particleFire1D(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 5)) // init - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); PartSys->setParticleSize(1); } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10593,8 +10424,6 @@ uint16_t mode_particleFire1D(void) { } PartSys->update(); // update and render - - return FRAMETIME; } static const char _data_FX_MODE_PS_FIRE1D[] PROGMEM = "PS Fire 1D@!,!,Cooling,Blur;,!;!;1;pal=35,sx=100,ix=50,c1=80,c2=100,c3=28,o1=1,o2=1"; @@ -10602,12 +10431,12 @@ static const char _data_FX_MODE_PS_FIRE1D[] PROGMEM = "PS Fire 1D@!,!,Cooling,Bl Particle based AR effect, swoop particles along the strip with selected frequency loudness by DedeHai (Damian Schneider) */ -uint16_t mode_particle1DsonicStream(void) { +void mode_particle1DsonicStream(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 255, 0, true)) // init, no additional data needed - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); PartSys->sources[0].source.x = 0; // at start //PartSys->sources[1].source.x = PartSys->maxX; // at end @@ -10616,7 +10445,7 @@ uint16_t mode_particle1DsonicStream(void) { else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10696,8 +10525,6 @@ uint16_t mode_particle1DsonicStream(void) { PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i], nullptr, &PartSys->advPartProps[i]); } } - - return FRAMETIME; } static const char _data_FX_MODE_PS_SONICSTREAM[] PROGMEM = "PS Sonic Stream@!,!,Color,Blur,Bin,Mod,Filter,Push;,!;!;1f;c3=0,o2=1"; @@ -10706,17 +10533,17 @@ static const char _data_FX_MODE_PS_SONICSTREAM[] PROGMEM = "PS Sonic Stream@!,!, Particle based AR effect, creates exploding particles on beats by DedeHai (Damian Schneider) */ -uint16_t mode_particle1DsonicBoom(void) { +void mode_particle1DsonicBoom(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 255, 0, true)) // init, no additional data needed - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -10777,7 +10604,6 @@ uint16_t mode_particle1DsonicBoom(void) { PartSys->sources[0].minLife = 200; PartSys->sources[0].maxLife = PartSys->sources[0].minLife + (((unsigned)SEGMENT.intensity * loudness * loudness) >> 13); PartSys->sources[0].source.hue = SEGMENT.aux0; - PartSys->sources[0].size = 1; //SEGMENT.speed>>3; uint32_t explosionsize = 4 + (PartSys->maxXpixel >> 2); explosionsize = hw_random16((explosionsize * loudness) >> 10); for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles @@ -10788,7 +10614,6 @@ uint16_t mode_particle1DsonicBoom(void) { SEGMENT.aux1 = 0; // reset edge detection PartSys->update(); // update and render (needs to be done before manipulation for initial particle spacing to be right) - return FRAMETIME; } static const char _data_FX_MODE_PS_SONICBOOM[] PROGMEM = "PS Sonic Boom@!,!,Color,Position,Bin,Mod,Filter,Blur;,!;!;1f;c2=63,c3=0,o2=1"; @@ -10796,17 +10621,17 @@ static const char _data_FX_MODE_PS_SONICBOOM[] PROGMEM = "PS Sonic Boom@!,!,Colo Particles bound by springs by DedeHai (Damian Schneider) */ -uint16_t mode_particleSpringy(void) { +void mode_particleSpringy(void) { ParticleSystem1D *PartSys = nullptr; if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init with advanced properties (used for spring forces) - return mode_static(); // allocation failed or is single pixel + FX_FALLBACK_STATIC; // allocation failed or is single pixel SEGENV.aux0 = SEGENV.aux1 = 0xFFFF; // invalidate settings } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == nullptr) - return mode_static(); // something went wrong, no data! + FX_FALLBACK_STATIC; // something went wrong, no data! // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setMotionBlur(220 * SEGMENT.check1); // anable motion blur @@ -10957,7 +10782,6 @@ uint16_t mode_particleSpringy(void) { } } PartSys->update(); // update and render - return FRAMETIME; } static const char _data_FX_MODE_PS_SPRINGY[] PROGMEM = "PS Springy@Stiffness,Damping,Density,Hue,Mode,Smear,XL,AR;,!;!;1f;pal=54,c2=0,c3=23"; @@ -11039,6 +10863,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_COLOR_SWEEP_RANDOM, &mode_color_sweep_random, _data_FX_MODE_COLOR_SWEEP_RANDOM); addEffect(FX_MODE_RUNNING_COLOR, &mode_running_color, _data_FX_MODE_RUNNING_COLOR); addEffect(FX_MODE_AURORA, &mode_aurora, _data_FX_MODE_AURORA); + addEffect(FX_MODE_COLORCLOUDS, &mode_ColorClouds, _data_FX_MODE_COLORCLOUDS); addEffect(FX_MODE_RUNNING_RANDOM, &mode_running_random, _data_FX_MODE_RUNNING_RANDOM); addEffect(FX_MODE_LARSON_SCANNER, &mode_larson_scanner, _data_FX_MODE_LARSON_SCANNER); addEffect(FX_MODE_RAIN, &mode_rain, _data_FX_MODE_RAIN); @@ -11095,7 +10920,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER); addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); #ifdef WLED_PS_DONT_REPLACE_1D_FX - addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); + addEffect(FX_MODE_ROLLINGBALLS, &mode_rolling_balls, _data_FX_MODE_ROLLINGBALLS); addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST); addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); #endif diff --git a/wled00/FX.h b/wled00/FX.h index bcbab69a59..ce7a222237 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -379,7 +379,8 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_PS1DSONICBOOM 215 #define FX_MODE_PS1DSPRINGY 216 #define FX_MODE_PARTICLEGALAXY 217 -#define MODE_COUNT 218 +#define FX_MODE_COLORCLOUDS 218 +#define MODE_COUNT 219 #define BLEND_STYLE_FADE 0x00 // universal @@ -466,7 +467,6 @@ class Segment { char *name; // segment name // runtime data - mutable unsigned long next_time; // millis() of next update mutable uint32_t step; // custom "step" var mutable uint32_t call; // call counter mutable uint16_t aux0; // custom var @@ -592,7 +592,6 @@ class Segment { , check3(false) , blendMode(0) , name(nullptr) - , next_time(0) , step(0) , call(0) , aux0(0) @@ -823,19 +822,18 @@ class Segment { // main "strip" class (108 bytes) class WS2812FX { - typedef uint16_t (*mode_ptr)(); // pointer to mode function + typedef void (*mode_ptr)(); // pointer to mode function typedef void (*show_callback)(); // pre show callback typedef struct ModeData { uint8_t _id; // mode (effect) id mode_ptr _fcn; // mode (effect) function const char *_data; // mode (effect) name and its UI control data - ModeData(uint8_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} + ModeData(uint8_t id, void (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} } mode_data_t; public: WS2812FX() : - paletteBlend(0), now(millis()), timebase(0), isMatrix(false), @@ -937,7 +935,7 @@ class WS2812FX { inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request - uint8_t paletteBlend; + // uint8_t paletteBlend; // obsolete - use global paletteBlend instead of strip.paletteBlend uint8_t getActiveSegmentsNum() const; uint8_t getFirstSelectedSegId() const; uint8_t getLastActiveSegmentId() const; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 063d3a6bb3..77e466588a 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -74,7 +74,7 @@ void WS2812FX::setUpMatrix() { size_t gapSize = 0; int8_t *gapTable = nullptr; - if (isFile && requestJSONBufferLock(20)) { + if (isFile && requestJSONBufferLock(JSON_LOCK_LEDGAP)) { DEBUG_PRINT(F("Reading LED gap from ")); DEBUG_PRINTLN(fileName); // read the array into global JSON buffer @@ -120,6 +120,9 @@ void WS2812FX::setUpMatrix() { for (unsigned i=0; i maxLedsOnBus) maxLedsOnBus = bus.count; + if (bus.driverType == 1) + i2sBusCount++; } } - DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount); - // we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3 - if (maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses - else useParallelI2S = false; // enforce single I2S - digitalCount = 0; + DEBUG_PRINTF_P(PSTR("Digital buses: %u, I2S buses: %u\n"), digitalCount, i2sBusCount); + + // Determine parallel vs single I2S usage (used for memory calculation only) + bool useParallelI2S = false; + #if defined(CONFIG_IDF_TARGET_ESP32S3) + // ESP32-S3 always uses parallel LCD driver for I2S + if (i2sBusCount > 0) { + useParallelI2S = true; + } + #else + if (i2sBusCount > 1) { + useParallelI2S = true; + } + #endif #endif DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize()); // create buses/outputs - unsigned mem = 0; - unsigned maxI2S = 0; - for (const auto &bus : busConfigs) { - unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer - mem += memB; - // estimate maximum I2S memory usage (only relevant for digital non-2pin busses) + unsigned mem = 0; // memory estimation including DMA buffer for I2S and pixel buffers + unsigned I2SdmaMem = 0; + for (auto &bus : busConfigs) { + // assign bus types: call to getI() determines bus types/drivers, allocates and tracks polybus channels + // store the result in iType for later use during bus creation (getI() must only be called once per BusConfig) + // note: this needs to be determined for all buses prior to creating them as it also determines parallel I2S usage + bus.iType = BusManager::getI(bus.type, bus.pins, bus.driverType); + } + for (auto &bus : busConfigs) { + bool use_placeholder = false; + unsigned busMemUsage = bus.memUsage(); // does not include DMA/RMT buffer but includes pixel buffers (segment buffer + global buffer) + mem += busMemUsage; + // estimate maximum I2S memory usage (only relevant for digital non-2pin busses when I2S is enabled) #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) - const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1)); - #elif defined(CONFIG_IDF_TARGET_ESP32S2) - const bool usesI2S = (useParallelI2S && digitalCount <= 8); - #else - const bool usesI2S = false; - #endif + bool usesI2S = (bus.iType & 0x01) == 0; // I2S bus types are even numbered, can't use bus.driverType == 1 as getI() may have defaulted to RMT if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) { #ifdef NPB_CONF_4STEP_CADENCE constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) #else constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) #endif - unsigned i2sCommonSize = stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1); - if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + unsigned i2sCommonMem = (stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1)); + if (useParallelI2S) i2sCommonMem *= 8; // parallel I2S uses 8 channels, requiring 8x the DMA buffer size (common buffer shared between all parallel busses) + if (i2sCommonMem > I2SdmaMem) I2SdmaMem = i2sCommonMem; } #endif - if (mem + maxI2S <= MAX_LED_MEMORY) { - BusManager::add(bus); - DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB); - } else { - errorFlag = ERR_NORAM_PX; // alert UI - DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); - break; + if (mem + I2SdmaMem > MAX_LED_MEMORY + 1024) { // +1k to allow some margin to not drop buses that are allowed in UI (calculation here includes bus overhead) + DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count); + errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: not enough memory for bus + use_placeholder = true; + } + if (BusManager::add(bus, use_placeholder) != -1) { + mem += BusManager::busses.back()->getBusSize(); + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count } } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage()); + DEBUG_PRINTF_P(PSTR("Estimated buses + pixel-buffers size: %uB\n"), mem + I2SdmaMem); busConfigs.clear(); busConfigs.shrink_to_fit(); @@ -1281,10 +1289,9 @@ void WS2812FX::service() { if (!seg.isActive()) continue; // last condition ensures all solid segments are updated at the same time - if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) + if (nowUp > _lastServiceShow + _frametime || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen // Effect blending @@ -1292,7 +1299,7 @@ void WS2812FX::service() { seg.beginDraw(prog); // set up parameters for get/setPixelColor() (will also blend colors and palette if blend style is FADE) _currentSegment = &seg; // set current segment for effect functions (SEGMENT & SEGENV) // workaround for on/off transition to respect blending style - frameDelay = (*_mode[seg.mode])(); // run new/current mode (needed for bri workaround) + _mode[seg.mode](); // run new/current mode (needed for bri workaround) seg.call++; // if segment is in transition and no old segment exists we don't need to run the old mode // (blendSegments() takes care of On/Off transitions and clipping) @@ -1303,14 +1310,11 @@ void WS2812FX::service() { segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress _currentSegment = segO; // set current segment // workaround for on/off transition to respect blending style - frameDelay = min(frameDelay, (unsigned)(*_mode[segO->mode])()); // run old mode (needed for bri workaround; semaphore!!) + _mode[segO->mode](); // run old mode (needed for bri workaround; semaphore!!) segO->call++; // increment old mode run counter Segment::modeBlend(false); // unset semaphore } - if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition } - - seg.next_time = nowUp + frameDelay; } _segment_index++; } @@ -1383,6 +1387,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const unsigned progInv = 0xFFFFU - progress; uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE uint8_t cct = topSegment.currentCCT(); + if (gammaCorrectCol) opacity = gamma8inv(opacity); // use inverse gamma on brightness for correct color scaling after gamma correction (see #5343 for details) Segment::setClippingRect(0, 0); // disable clipping by default @@ -1723,7 +1728,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { BusManager::setBrightness(scaledBri(b)); if (!direct) { unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon + if (t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon } } @@ -1824,6 +1829,10 @@ void WS2812FX::resetSegments() { if (isServicing()) return; _segments.clear(); // destructs all Segment as part of clearing _segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1); + if(_segments.size() == 0) { + _segments.emplace_back(); // if out of heap, create a default segment + errorFlag = ERR_NORAM_PX; + } _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } @@ -1846,7 +1855,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { for (size_t i = s; i < BusManager::getNumBusses(); i++) { const Bus *bus = BusManager::getBus(i); - if (!bus || !bus->isOk()) break; + if (!bus) break; segStarts[s] = bus->getStart(); segStops[s] = segStarts[s] + bus->getLength(); @@ -1982,7 +1991,7 @@ bool WS2812FX::deserializeMap(unsigned n) { return false; } - if (!isFile || !requestJSONBufferLock(7)) return false; + if (!isFile || !requestJSONBufferLock(JSON_LOCK_LEDMAP)) return false; StaticJsonDocument<64> filter; filter[F("width")] = true; diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 626d936b9a..e2782bdd45 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -48,6 +48,7 @@ ParticleSystem2D::ParticleSystem2D(uint32_t width, uint32_t height, uint32_t num sources[i].source.ttl = 1; //set source alive sources[i].sourceFlags.asByte = 0; // all flags disabled } + perParticleSize = isadvanced; // enable per particle size by default if using advanced properties (FX can disable if needed) } @@ -79,9 +80,8 @@ void ParticleSystem2D::update(void) { } // update function for fire animation -void ParticleSystem2D::updateFire(const uint8_t intensity,const bool renderonly) { - if (!renderonly) - fireParticleupdate(); +void ParticleSystem2D::updateFire(const uint8_t intensity) { + fireParticleupdate(); fireIntesity = intensity > 0 ? intensity : 1; // minimum of 1, zero checking is used in render function render(); } @@ -1096,7 +1096,11 @@ bool allocateParticleSystemMemory2D(uint32_t numparticles, uint32_t numsources, // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources, uint32_t additionalbytes, bool advanced, bool sizecontrol) { PSPRINT("PS 2D init "); - if (!strip.isMatrix) return false; // only for 2D + if (!strip.isMatrix) { + errorFlag = ERR_NOT_IMPL; // TODO: need a better error code if more codes are added + SEGMENT.deallocateData(); // deallocate any data to make sure data is null (there is no valid PS in data and data can only be checked for null) + return false; // only for 2D + } uint32_t cols = SEGMENT.virtualWidth(); uint32_t rows = SEGMENT.virtualHeight(); uint32_t pixels = cols * rows; @@ -1156,7 +1160,7 @@ ParticleSystem1D::ParticleSystem1D(uint32_t length, uint32_t numberofparticles, sources[i].source.ttl = 1; //set source alive sources[i].sourceFlags.asByte = 0; // all flags disabled } - + perParticleSize = isadvanced; // enable per particle size by default so FX do not need to set this explicitly. FX can disable by setting global size. if (isadvanced) { for (uint32_t i = 0; i < numParticles; i++) { advPartProps[i].sat = 255; // set full saturation @@ -1844,7 +1848,11 @@ bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) // note: percentofparticles is in uint8_t, for example 191 means 75%, (deafaults to 255 or 100% meaning one particle per pixel), can be more than 100% (but not recommended, can cause out of memory) bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles, const uint32_t additionalbytes, const bool advanced) { - if (SEGLEN == 1) return false; // single pixel not supported + if (SEGLEN == 1) { + errorFlag = ERR_NOT_IMPL; // TODO: need a better error code if more codes are added + SEGMENT.deallocateData(); // deallocate any data to make sure data is null (there is no valid PS in data and data can only be checked for null) + return false; // single pixel not supported + } uint32_t numparticles = calculateNumberOfParticles1D(fractionofparticles, advanced); uint32_t numsources = calculateNumberOfSources1D(requestedsources); bool allocsuccess = false; diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index 6a22109a93..afe683c0b1 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -143,7 +143,7 @@ class ParticleSystem2D { ParticleSystem2D(const uint32_t width, const uint32_t height, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false, const bool sizecontrol = false); // constructor // note: memory is allcated in the FX function, no deconstructor needed void update(void); //update the particles according to set options and render to the matrix - void updateFire(const uint8_t intensity, const bool renderonly); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips) + void updateFire(const uint8_t intensity); // update function for fire void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions void particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function // particle emitters diff --git a/wled00/NodeStruct.h b/wled00/NodeStruct.h index 34f73ab418..9647162054 100644 --- a/wled00/NodeStruct.h +++ b/wled00/NodeStruct.h @@ -15,6 +15,22 @@ #define NODE_TYPE_ID_ESP32S3 34 #define NODE_TYPE_ID_ESP32C3 35 +// updated node types from the ESP Easy project +// https://github.com/letscontrolit/ESPEasy/blob/mega/src/src/DataTypes/NodeTypeID.h +//#define NODE_TYPE_ID_ESP32 33 +//#define NODE_TYPE_ID_ESP32S2 34 +//#define NODE_TYPE_ID_ESP32C3 35 +//#define NODE_TYPE_ID_ESP32S3 36 +#define NODE_TYPE_ID_ESP32C2 37 +#define NODE_TYPE_ID_ESP32H2 38 +#define NODE_TYPE_ID_ESP32C6 39 +#define NODE_TYPE_ID_ESP32C61 40 +#define NODE_TYPE_ID_ESP32C5 41 +#define NODE_TYPE_ID_ESP32P4 42 +#define NODE_TYPE_ID_ESP32P4r3 45 +#define NODE_TYPE_ID_ESP32H21 43 +#define NODE_TYPE_ID_ESP32H4 44 + /*********************************************************************************************\ * NodeStruct \*********************************************************************************************/ diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5cc0eb2c95..7d0d976381 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -9,14 +9,6 @@ #include "src/dependencies/network/Network.h" // for isConnected() (& WiFi) #include "driver/ledc.h" #include "soc/ledc_struct.h" - #if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)) - #define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS) - #define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock) - extern xSemaphoreHandle _ledc_sys_lock; - #else - #define LEDC_MUTEX_LOCK() - #define LEDC_MUTEX_UNLOCK() - #endif #endif #ifdef ESP8266 #include "core_esp8266_waveform.h" @@ -25,21 +17,17 @@ #include "bus_wrapper.h" #include "wled.h" -extern char cmDNS[]; -extern bool cctICused; -extern bool useParallelI2S; - // functions to get/set bits in an array - based on functions created by Brandon for GOL // toDo : make this a class that's completely defined in a header file // note: these functions are automatically inline by the compiler -bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value +static inline bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value size_t byteIndex = position >> 3; // divide by 8 unsigned bitIndex = position & 0x07; // modulo 8 uint8_t byteValue = byteArray[byteIndex]; return (byteValue >> bitIndex) & 1; } -void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr +static inline void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr //if (byteArray == nullptr) return; size_t byteIndex = position >> 3; // divide by 8 unsigned bitIndex = position & 0x07; // modulo 8 @@ -49,11 +37,11 @@ void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bi byteArray[byteIndex] &= ~(1 << bitIndex); } -size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits +static inline size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits return (num_bits + 7) >> 3; } -void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value +static inline void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value if (byteArray == nullptr) return; size_t len = getBitArrayBytes(numBits); if (value) memset(byteArray, 0xFF, len); @@ -90,42 +78,67 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format } - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); + // CCT blending modes (_cctBlend): + // blend<0: ww: ▓▓▒░__ | blend=0: ww: ▓▒▒░░ | blend>0 ww: ▓▓▓▒░ + // cw: __░▒▓▓ | cw: ░░▒▒▓ | cw: ░▒▓▓▓ + int32_t ww_val, cw_val; + if (_cctBlend < 0) { + uint16_t range = 255 - 2 * (uint16_t)(-_cctBlend); + if (range > 255) range = 255; // prevent overflow + ww_val = range ? ((int32_t)(255 + _cctBlend - cct) * 255) / range : (cct < 128 ? 255 : 0); // exclusive blending + cw_val = 255 - ww_val; + } else { + ww_val = _cctBlend ? ((int32_t)(255 - cct) * 255) / (255 - _cctBlend) : 255 - cct; // additive blending + cw_val = _cctBlend ? ((int32_t) cct * 255) / (255 - _cctBlend) : cct; + } + ww = (uint8_t)(ww_val < 0 ? 0 : ww_val > 255 ? 255 : ww_val); + cw = (uint8_t)(cw_val < 0 ? 0 : cw_val > 255 ? 255 : cw_val); ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; } -uint32_t Bus::autoWhiteCalc(uint32_t c) const { +// calculates white channel and CCT values based on given settings +uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const { unsigned aWM = _autoWhiteMode; if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; - if (aWM == RGBW_MODE_MANUAL_ONLY) return c; + CRGBW cIn = c; // save original color for CCT calculation unsigned w = W(c); - //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) - if (w > 0 && aWM == RGBW_MODE_DUAL) return c; - unsigned r = R(c); - unsigned g = G(c); - unsigned b = B(c); - if (aWM == RGBW_MODE_MAX) return RGBW32(r, g, b, r > g ? (r > b ? r : b) : (g > b ? g : b)); // brightest RGB channel - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode - return RGBW32(r, g, b, w); + if (aWM != RGBW_MODE_MANUAL_ONLY) { + unsigned r = R(c); // note: using uint8_t generates larger code + unsigned g = G(c); + unsigned b = B(c); + if (aWM == RGBW_MODE_DUAL && w > 0) { + //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) + } else if (aWM == RGBW_MODE_MAX) { + w = r > g ? (r > b ? r : b) : (g > b ? g : b); // brightest RGB channel + } else { + w = r < g ? (r < b ? r : b) : (g < b ? g : b); // darkest RGB channel + if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode + } + c = RGBW32(r, g, b, w); + } + if (_hasCCT) { + cIn.w = w; // need original rgb values in case CCT is derived from RGB + calculateCCT(cIn, ww, cw); + } + return c; } -BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) +BusDigital::BusDigital(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) , _skip(bc.skipAmount) //sacrificial pixels , _colorOrder(bc.colorOrder) , _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsMax(bc.milliAmpsMax) +, _driverType(bc.driverType) // Store driver preference (0=RMT, 1=I2S) { DEBUGBUS_PRINTLN(F("Bus: Creating digital bus.")); if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } + _iType = bc.iType; // reuse the iType that was determined by polyBus in getI() in finalizeInit() + if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; } + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; } _frequencykHz = 0U; _colorSum = 0; @@ -139,28 +152,30 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) _pins[1] = bc.pins[1]; _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined } - _iType = PolyBus::getI(bc.type, _pins, nr); - if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; } + _hasRgb = hasRGB(bc.type); _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip); _valid = (_busPtr != nullptr) && bc.count > 0; // fix for wled#4759 if (_valid) for (unsigned i = 0; i < _skip; i++) { PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here) } - DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"), - _valid?"S":"Uns", - (int)nr, + else { + cleanup(); + } + DEBUGBUS_PRINTF_P(PSTR("Bus len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u, driver:%s] mA=%d/%d %s\n"), (int)bc.count, (int)bc.type, (int)_hasRgb, (int)_hasWhite, (int)_hasCCT, (unsigned)_pins[0], is2Pin(bc.type)?(unsigned)_pins[1]:255U, (unsigned)_iType, - (int)_milliAmpsPerLed, (int)_milliAmpsMax + isI2S() ? "I2S" : "RMT", + (int)_milliAmpsPerLed, (int)_milliAmpsMax, + _valid ? " " : "FAILED" ); } @@ -211,15 +226,20 @@ void BusDigital::applyBriLimit(uint8_t newBri) { if (newBri < 255) { _NPBbri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used) - uint8_t cctWW = 0, cctCW = 0; + uint16_t wwcw = 0; unsigned hwLen = _len; if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus for (unsigned i = 0; i < hwLen; i++) { uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped - uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); + uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); // Note: if ABL would be calculated as a seperate loop (as it was before) it is slower but could use original color, making it more color-accurate + if (hasCCT()) { + uint8_t cctWW, cctCW; + Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) | Note: if using "accurate" white calculation mode, approximateKelvinFromRGB can be very inaccurate (white is subtracted) + wwcw = ((cctCW + 1) * newBri) & 0xFF00; // apply brightness to CCT (leave it in upper byte for 16bit NeoPixelBus value) + wwcw |= ((cctWW + 1) * newBri) >> 8; + } c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); - PolyBus::setPixelColor(_busPtr, _iType, i, c, co, (cctCW<<8) | cctWW); // repaint all pixels with new brightness + PolyBus::setPixelColor(_busPtr, _iType, i, c, co, wwcw); // repaint all pixels with new brightness } } @@ -246,12 +266,21 @@ void BusDigital::setStatusPixel(uint32_t c) { } } +// note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - if (hasWhite()) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT + uint8_t cctWW = 0, cctCW = 0; + uint16_t wwcw = 0; + if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW); c = color_fade(c, _bri, true); // apply brightness + if (hasCCT()) { + wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightness to CCT (store CW in upper byte) + wwcw |= ((cctWW + 1) * _bri) >> 8; + if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w + } + if (BusManager::_useABL) { // if using ABL, sum all color channels to estimate current and limit brightness in show() uint8_t r = R(c), g = G(c), b = B(c); @@ -275,13 +304,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - uint16_t wwcw = 0; - if (hasCCT()) { - uint8_t cctWW = 0, cctCW = 0; - Bus::calculateCCT(c, cctWW, cctCW); - wwcw = (cctCW<<8) | cctWW; - if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); - } + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } @@ -351,6 +374,10 @@ std::vector BusDigital::getLEDTypes() { }; } +bool BusDigital::isI2S() { + return (_iType & 0x01) == 0; // I2S types have even iType values +} + void BusDigital::begin() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); @@ -443,11 +470,13 @@ BusPwm::BusPwm(const BusConfig &bc) void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } + uint8_t cctWW, cctCW; + if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c, cctWW, cctCW); uint8_t r = R(c), g = G(c), b = B(c), w = W(c); + // note: no color scaling, brightness is applied in show() switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -458,14 +487,18 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { _data[0] = w; _data[1] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; } else { - Bus::calculateCCT(c, _data[0], _data[1]); + _data[0] = cctWW; + _data[1] = cctCW; } break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white if (cctICused) _data[4] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; - else - Bus::calculateCCT(c, w, _data[4]); + else { + w = cctWW; + _data[4] = cctCW; + } + // fall through to set RGBW channels case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB @@ -532,7 +565,7 @@ void BusPwm::show() { unsigned duty = (_data[i] * pwmBri) / 255; unsigned deadTime = 0; - if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend == 0) { + if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend <= 0) { // add dead time between signals (when using dithering, two full 8bit pulses are required) deadTime = (1+dithering) << bitShift; // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap @@ -621,9 +654,7 @@ BusOnOff::BusOnOff(const BusConfig &bc) void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - c = autoWhiteCalc(c); - uint8_t r = R(c), g = G(c), b = B(c), w = W(c); - _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; + _data = (c > 0) && bool(_bri) ? 0xFF : 0; // if any color channel is on and brightness is not zero, set to on } uint32_t BusOnOff::getPixelColor(unsigned pix) const { @@ -683,7 +714,8 @@ BusNetwork::BusNetwork(const BusConfig &bc) void BusNetwork::setPixelColor(unsigned pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (_hasWhite) c = autoWhiteCalc(c); + uint8_t ww, cw; // dummy, unused + if (_hasWhite) c = autoWhiteCalc(c, ww, cw); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT unsigned offset = pix * _UDPchannels; _data[offset] = R(c); @@ -1105,50 +1137,44 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const { #endif // *************************************************************************** -//utility to get the approx. memory usage of a given BusConfig -size_t BusConfig::memUsage(unsigned nr) const { +BusPlaceholder::BusPlaceholder(const BusConfig &bc) +: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, bc.refreshReq) +, _colorOrder(bc.colorOrder) +, _skipAmount(bc.skipAmount) +, _driverType(bc.driverType) +, _frequency(bc.frequency) +, _milliAmpsPerLed(bc.milliAmpsPerLed) +, _milliAmpsMax(bc.milliAmpsMax) +, _text(bc.text) +{ + memcpy(_pins, bc.pins, sizeof(_pins)); +} + +size_t BusPlaceholder::getPins(uint8_t* pinArray) const { + size_t nPins = Bus::getNumberOfPins(_type); + if (pinArray) { + for (size_t i = 0; i < nPins; i++) pinArray[i] = _pins[i]; + } + return nPins; +} + +//utility to get the approx. memory usage of a given BusConfig inclduding segmentbuffer and global buffer (4 bytes per pixel) +size_t BusConfig::memUsage() const { + size_t mem = (count + skipAmount) * 8; // 8 bytes per pixel for segment + global buffer if (Bus::isVirtual(type)) { - return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); + mem += sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); // note: getNumberOfChannels() includes CCT channel if applicable but virtual buses do not use CCT channel buffer } else if (Bus::isDigital(type)) { // if any of digital buses uses I2S, there is additional common I2S DMA buffer not accounted for here - return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)); + mem += sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, iType); } else if (Bus::isOnOff(type)) { - return sizeof(BusOnOff); + mem += sizeof(BusOnOff); } else { - return sizeof(BusPwm); + mem += sizeof(BusPwm); } + return mem; } - -size_t BusManager::memUsage() { - // when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers - // front buffers are always allocated per bus - unsigned size = 0; - unsigned maxI2S = 0; - #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - unsigned digitalCount = 0; - #endif - for (const auto &bus : busses) { - size += bus->getBusSize(); - #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - if (bus->isDigital() && !bus->is2Pin()) { - digitalCount++; - if ((PolyBus::isParallelI2S1Output() && digitalCount <= 8) || (!PolyBus::isParallelI2S1Output() && digitalCount == 1)) { - #ifdef NPB_CONF_4STEP_CADENCE - constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) - #else - constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) - #endif - unsigned i2sCommonSize = stepFactor * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); - if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; - } - } - #endif - } - return size + maxI2S; -} - -int BusManager::add(const BusConfig &bc) { +int BusManager::add(const BusConfig &bc, bool placeholder) { DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses()); unsigned digital = 0; unsigned analog = 0; @@ -1158,15 +1184,19 @@ int BusManager::add(const BusConfig &bc) { if (bus->isDigital() && !bus->is2Pin()) digital++; if (bus->is2Pin()) twoPin++; } - if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1; - if (Bus::isVirtual(bc.type)) { + digital += (Bus::isDigital(bc.type) && !Bus::is2Pin(bc.type)); + analog += (Bus::isPWM(bc.type) ? Bus::numPWMPins(bc.type) : 0); + if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; // TODO: add errorFlag here + if (placeholder) { + busses.push_back(make_unique(bc)); + } else if (Bus::isVirtual(bc.type)) { busses.push_back(make_unique(bc)); #ifdef WLED_ENABLE_HUB75MATRIX } else if (Bus::isHub75(bc.type)) { busses.push_back(make_unique(bc)); #endif } else if (Bus::isDigital(bc.type)) { - busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); + busses.push_back(make_unique(bc)); } else if (Bus::isOnOff(bc.type)) { busses.push_back(make_unique(bc)); } else { @@ -1204,49 +1234,35 @@ String BusManager::getLEDTypesJSONString() { return json; } -void BusManager::useParallelOutput() { - DEBUGBUS_PRINTLN(F("Bus: Enabling parallel I2S.")); - PolyBus::setParallelI2S1Output(); +uint8_t BusManager::getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference) { + return PolyBus::getI(busType, pins, driverPreference); } - -bool BusManager::hasParallelOutput() { - return PolyBus::isParallelI2S1Output(); -} - //do not call this method from system context (network callback) void BusManager::removeAll() { DEBUGBUS_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); busses.clear(); - PolyBus::setParallelI2S1Output(false); + #ifndef ESP8266 + // Reset channel tracking for fresh allocation + PolyBus::resetChannelTracking(); + #endif } #ifdef ESP32_DATA_IDLE_HIGH // #2478 // If enabled, RMT idle level is set to HIGH when off // to prevent leakage current when using an N-channel MOSFET to toggle LED power +// since I2S outputs are known only during config of buses, lets just assume RMT is used for digital buses +// unused RMT channels should have no effect void BusManager::esp32RMTInvertIdle() { bool idle_out; unsigned rmt = 0; unsigned u = 0; for (auto &bus : busses) { if (bus->getLength()==0 || !bus->isDigital() || bus->is2Pin()) continue; - #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM - if (u > 1) return; - rmt = u; - #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, only has 1 I2S bus, supported in NPB - if (u > 3) return; - rmt = u; - #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, has 2 I2S but NPB does not support them ATM - if (u > 3) return; - rmt = u; - #else - unsigned numI2S = !PolyBus::isParallelI2S1Output(); // if using parallel I2S, RMT is used 1st - if (numI2S > u) continue; - if (u > 7 + numI2S) return; - rmt = u - numI2S; - #endif + if (static_cast(bus.get())->isI2S()) continue; + if (u >= WLED_MAX_RMT_CHANNELS) return; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(rmt); rmt_idle_level_t lvl; @@ -1266,7 +1282,7 @@ void BusManager::on() { if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (auto &bus : busses) { uint8_t pins[2] = {255,255}; - if (bus->isDigital() && bus->getPins(pins)) { + if (bus->isDigital() && bus->getPins(pins) && bus->isOk()) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital &b = static_cast(*bus); b.begin(); @@ -1361,7 +1377,7 @@ void BusManager::initializeABL() { _useABL = true; // at least one bus has ABL set uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus for (auto &bus : busses) { - if (bus->isDigital()) { + if (bus->isDigital() && bus->isOk()) { BusDigital &busd = static_cast(*bus); uint32_t busLength = busd.getLength(); uint32_t busDemand = busLength * busd.getLEDCurrent(); @@ -1421,12 +1437,18 @@ void BusManager::applyABL() { ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; } - +#ifndef ESP8266 +// PolyBus channel tracking for dynamic allocation bool PolyBus::_useParallelI2S = false; - +uint8_t PolyBus::_rmtChannelsAssigned = 0; // number of RMT channels assigned durig getI() check +uint8_t PolyBus::_rmtChannel = 0; // number of RMT channels actually used during bus creation in create() +uint8_t PolyBus::_i2sChannelsAssigned = 0; // number of I2S channels assigned durig getI() check +uint8_t PolyBus::_parallelBusItype = 0; // type I_NONE +uint8_t PolyBus::_2PchannelsAssigned = 0; +#endif // Bus static member definition -int16_t Bus::_cct = -1; -uint8_t Bus::_cctBlend = 0; // 0 - 127 +int16_t Bus::_cct = -1; // -1 means use approximateKelvinFromRGB(), 0-255 is standard, >1900 use colorBalanceFromKelvin() +int8_t Bus::_cctBlend = 0; // -128 to +127 uint8_t Bus::_gAWM = 255; uint16_t BusDigital::_milliAmpsTotal = 0; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 87d39fe34b..d5ac31a670 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -133,14 +133,15 @@ class Bus { virtual void setColorOrder(uint8_t co) {} virtual uint32_t getPixelColor(unsigned pix) const { return 0; } virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; } - virtual uint16_t getLength() const { return isOk() ? _len : 0; } + virtual uint16_t getLength() const { return _len; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual unsigned skippedLeds() const { return 0; } virtual uint16_t getFrequency() const { return 0U; } virtual uint16_t getLEDCurrent() const { return 0; } virtual uint16_t getUsedCurrent() const { return 0; } virtual uint16_t getMaxCurrent() const { return 0; } - virtual size_t getBusSize() const { return sizeof(Bus); } + virtual uint8_t getDriverType() const { return 0; } // Default to RMT (0) for non-digital buses + virtual size_t getBusSize() const { return sizeof(Bus); } // currently unused virtual const String getCustomText() const { return String(); } inline bool hasRGB() const { return _hasRgb; } @@ -152,6 +153,7 @@ class Bus { inline bool isPWM() const { return isPWM(_type); } inline bool isVirtual() const { return isVirtual(_type); } inline bool is16bit() const { return is16bit(_type); } + virtual bool isPlaceholder() const { return false; } inline bool mustRefresh() const { return mustRefresh(_type); } inline void setReversed(bool reversed) { _reversed = reversed; } inline void setStart(uint16_t start) { _start = start; } @@ -199,9 +201,9 @@ class Bus { static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } - static inline uint8_t getCCTBlend() { return (_cctBlend * 100 + 64) / 127; } // returns 0-100, 100% = 127. +64 for rounding - static inline void setCCTBlend(uint8_t b) { // input is 0-100 - _cctBlend = (std::min((int)b,100) * 127 + 50) / 100; // +50 for rounding, b=100% -> 127 + static inline int8_t getCCTBlend() { return (_cctBlend * 100 + (_cctBlend >= 0 ? 64 : -64)) / 127; } // returns -100 to +100, +/-100% = +/-127. +/-64 for rounding + static inline void setCCTBlend(int8_t b) { // input is -100 to +100 + _cctBlend = (std::max(-100, std::min(100, (int)b)) * 127 + (b >= 0 ? 50 : -50)) / 100; // +/-50 for rounding, b=+/-100% -> +/-127 //compile-time limiter for hardware that can't power both white channels at max #ifdef WLED_MAX_CCT_BLEND if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; @@ -230,19 +232,20 @@ class Bus { // [0,255] is the exact CCT value where 0 means warm and 255 cold // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) static int16_t _cct; - // _cctBlend determines WW/CW blending: + // _cctBlend determines WW/CW blending, see calculateCCT() + // < 0 - linear blending in center, single white at both ends, single white zone extends with decreased value (-127 min) // 0 - linear (CCT 127 => 50% warm, 50% cold) // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) - static uint8_t _cctBlend; + static int8_t _cctBlend; - uint32_t autoWhiteCalc(uint32_t c) const; + uint32_t autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const; }; class BusDigital : public Bus { public: - BusDigital(const BusConfig &bc, uint8_t nr); + BusDigital(const BusConfig &bc); ~BusDigital() { cleanup(); } void show() override; @@ -258,10 +261,12 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + uint8_t getDriverType() const override { return _driverType; } void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; } void estimateCurrent(); // estimate used current from summed colors void applyBriLimit(uint8_t newBri); size_t getBusSize() const override; + bool isI2S(); // true if this bus uses I2S driver void begin() override; void cleanup(); @@ -272,6 +277,7 @@ class BusDigital : public Bus { uint8_t _colorOrder; uint8_t _pins[2]; uint8_t _iType; + uint8_t _driverType; // 0=RMT (default), 1=I2S uint16_t _frequencykHz; uint16_t _milliAmpsMax; uint8_t _milliAmpsPerLed; @@ -372,6 +378,41 @@ class BusNetwork : public Bus { #endif }; +// Placeholder for buses that we can't construct due to resource limitations +// This preserves the configuration so it can be read back to the settings pages +// Function calls "mimic" the replaced bus, isPlaceholder() can be used to identify a placeholder +class BusPlaceholder : public Bus { + public: + BusPlaceholder(const BusConfig &bc); + + // Actual calls are stubbed out + void setPixelColor(unsigned pix, uint32_t c) override {}; + void show() override {}; + + // Accessors + uint8_t getColorOrder() const override { return _colorOrder; } + size_t getPins(uint8_t* pinArray) const override; + unsigned skippedLeds() const override { return _skipAmount; } + uint16_t getFrequency() const override { return _frequency; } + uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } + uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + uint8_t getDriverType() const override { return _driverType; } + const String getCustomText() const override { return _text; } + bool isPlaceholder() const override { return true; } + + size_t getBusSize() const override { return sizeof(BusPlaceholder); } + + private: + uint8_t _colorOrder; + uint8_t _skipAmount; + uint8_t _pins[OUTPUT_MAX_PINS]; + uint8_t _driverType; + uint16_t _frequency; + uint8_t _milliAmpsPerLed; + uint16_t _milliAmpsMax; + String _text; +}; + #ifdef WLED_ENABLE_HUB75MATRIX class BusHub75Matrix : public Bus { public: @@ -421,9 +462,11 @@ struct BusConfig { uint16_t frequency; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; + uint8_t driverType; // 0=RMT (default), 1=I2S + uint8_t iType; // internal bus type (I_*) determined during memory estimation, used for bus creation String text; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "") + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, uint8_t driver=0, String sometext = "") : count(std::max(len,(uint16_t)1)) , start(pstart) , colorOrder(pcolorOrder) @@ -433,13 +476,15 @@ struct BusConfig { , frequency(clock_kHz) , milliAmpsPerLed(maPerLed) , milliAmpsMax(maMax) + , driverType(driver) + , iType(0) // default to I_NONE , text(sometext) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) size_t nPins = Bus::getNumberOfPins(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; - DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), + DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d, driver:%s)\n"), (int)start, (int)(start+len), (int)type, (int)colorOrder, @@ -447,14 +492,15 @@ struct BusConfig { (int)skipAmount, (int)autoWhite, (int)frequency, - (int)milliAmpsPerLed, (int)milliAmpsMax + (int)milliAmpsPerLed, (int)milliAmpsMax, + driverType == 0 ? "RMT" : "I2S" ); } //validates start and length and extends total if needed bool adjustBounds(uint16_t& total) { if (!count) count = 1; - if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; + if (!Bus::isVirtual(type) && count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; if (start >= MAX_LEDS) return false; //limit length of strip if it would exceed total permissible LEDs if (start + count > MAX_LEDS) count = MAX_LEDS - start; @@ -463,7 +509,7 @@ struct BusConfig { return true; } - size_t memUsage(unsigned nr = 0) const; + size_t memUsage() const; }; @@ -494,7 +540,6 @@ namespace BusManager { return j; } - size_t memUsage(); inline uint16_t currentMilliamps() { return _gMilliAmpsUsed + MA_FOR_ESP; } //inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; } inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL) @@ -502,12 +547,11 @@ namespace BusManager { void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized void applyABL(); // apply automatic brightness limiter, global or per bus - void useParallelOutput(); // workaround for inaccessible PolyBus - bool hasParallelOutput(); // workaround for inaccessible PolyBus + uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference); // workaround for access to PolyBus function from FX_fcn.cpp //do not call this method from system context (network callback) void removeAll(); - int add(const BusConfig &bc); + int add(const BusConfig &bc, bool placeholder); void on(); void off(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index b2ff947418..0ecd4f986d 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -339,11 +339,17 @@ //handles pointer type conversion for all possible bus types class PolyBus { private: - static bool _useParallelI2S; - + #ifndef ESP8266 + static bool _useParallelI2S; // use parallel I2S/LCD (8 channels) + static uint8_t _rmtChannelsAssigned; // RMT channel tracking for dynamic allocation + static uint8_t _rmtChannel; // physical RMT channel to use during bus creation + static uint8_t _i2sChannelsAssigned; // I2S channel tracking for dynamic allocation + static uint8_t _parallelBusItype; // parallel output does not allow mixed LED types, track I_Type + static uint8_t _2PchannelsAssigned; // 2-Pin (SPI) channel assigned: first one gets the hardware SPI, others use bit-banged SPI + // note on 2-Pin Types: all supported types except WS2801 use start/stop or latch frames, speed is not critical. WS2801 uses a 500us timeout and is prone to flickering if bit-banged too slow. + // TODO: according to #4863 using more than one bit-banged output can cause glitches even in APA102. This needs investigation as from a hardware perspective all but WS2801 should be immune to timing issues. + #endif public: - static inline void setParallelI2S1Output(bool b = true) { _useParallelI2S = b; } - static inline bool isParallelI2S1Output(void) { return _useParallelI2S; } // initialize SPI bus speed for DotStar methods template @@ -476,21 +482,7 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { - // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation - - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (_useParallelI2S && (channel >= 8)) { - // Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number - channel -= 8; - } - #endif - - #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) - // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 - #endif - + static void* create(uint8_t busType, uint8_t* pins, uint16_t len) { void* busPtr = nullptr; switch (busType) { case I_NONE: break; @@ -546,18 +538,18 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses - case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; - case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break; + case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break; // I2S1 bus or paralell buses #ifndef CONFIG_IDF_TARGET_ESP32C3 case I_32_I2_NEO_3: if (_useParallelI2S) busPtr = new B_32_IP_NEO_3(len, pins[0]); else busPtr = new B_32_I2_NEO_3(len, pins[0]); break; @@ -1118,6 +1110,9 @@ class PolyBus { static unsigned getDataSize(void* busPtr, uint8_t busType) { unsigned size = 0; + #ifdef ARDUINO_ARCH_ESP32 + size = 100; // ~100bytes for NPB internal structures (measured for both I2S and RMT, much smaller and more variable on ESP8266) + #endif switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -1172,32 +1167,32 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses (front + back + small system managed RMT) - case I_32_RN_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_32_RN_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) + case I_32_RN_NEO_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_NEO_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_400_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM2_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_4: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_APA106_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_FW6_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_2805_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1914_3: size += (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_SM16825_5: size += (static_cast(busPtr))->PixelsSize()*2; break; + // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) not: for parallel I2S only the largest bus counts for DMA memory, this is not done correctly here, also assumes 3-step cadence #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_NEO_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_400_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM1_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM2_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_UCS_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_UCS_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_APA106_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_FW6_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_2805_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_TM1914_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; - case I_32_I2_SM16825_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_400_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM2_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_4: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_APA106_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_FW6_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_2805_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1914_3: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_SM16825_5: size += (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; #endif #endif case I_HS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; @@ -1255,6 +1250,7 @@ class PolyBus { case I_8266_DM_2805_5 : size = (size + 2*count)*5; break; case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; #else + // note: RMT and I2S buses use ~100 bytes of internal NPB memory each, not included here for simplicity // RMT buses (1x front and 1x back buffer, does not include small RMT buffer) case I_32_RN_NEO_4 : // fallthrough case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels @@ -1263,7 +1259,7 @@ class PolyBus { case I_32_RN_FW6_5 : // fallthrough case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels - // I2S1 bus or paralell I2S1 buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD) + // I2S bus or paralell I2S buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD) #ifndef CONFIG_IDF_TARGET_ESP32C3 case I_32_I2_NEO_3 : // fallthrough case I_32_I2_400_3 : // fallthrough @@ -1282,30 +1278,37 @@ class PolyBus { } return size; } - - //gives back the internal type index (I_XX_XXX_X above) for the input - static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t num = 0) { +#ifndef ESP8266 + // Reset channel tracking (call before adding buses) + static void resetChannelTracking() { + _useParallelI2S = false; + _rmtChannelsAssigned = 0; + _rmtChannel = 0; + _i2sChannelsAssigned = 0; + _parallelBusItype = I_NONE; + _2PchannelsAssigned = 0; + } +#endif + // reserves and gives back the internal type index (I_XX_XXX_X above) for the input based on bus type and pins + static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference) { if (!Bus::isDigital(busType)) return I_NONE; + uint8_t t = I_NONE; if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; #ifdef ESP8266 if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; #else - // temporary hack to limit use of hardware SPI to a single SPI peripheral (HSPI): only allow ESP32 hardware serial on segment 0 - // SPI global variable is normally linked to VSPI on ESP32 (or FSPI C3, S3) - if (!num) isHSPI = true; + if (_2PchannelsAssigned == 0) isHSPI = true; // first 2-pin channel uses hardware SPI + _2PchannelsAssigned++; #endif - uint8_t t = I_NONE; switch (busType) { case TYPE_APA102: t = I_SS_DOT_3; break; case TYPE_LPD8806: t = I_SS_LPD_3; break; case TYPE_LPD6803: t = I_SS_LPO_3; break; case TYPE_WS2801: t = I_SS_WS1_3; break; case TYPE_P9813: t = I_SS_P98_3; break; - default: t=I_NONE; } if (t > I_NONE && isHSPI) t--; //hardware SPI has one smaller ID than software - return t; } else { #ifdef ESP8266 uint8_t offset = pins[0] -1; //for driver: 0 = uart0, 1 = uart1, 2 = dma, 3 = bitbang @@ -1315,96 +1318,87 @@ class PolyBus { case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: - return I_8266_U0_NEO_3 + offset; + t = I_8266_U0_NEO_3 + offset; break; case TYPE_SK6812_RGBW: - return I_8266_U0_NEO_4 + offset; + t = I_8266_U0_NEO_4 + offset; break; case TYPE_WS2811_400KHZ: - return I_8266_U0_400_3 + offset; + t = I_8266_U0_400_3 + offset; break; case TYPE_TM1814: - return I_8266_U0_TM1_4 + offset; + t = I_8266_U0_TM1_4 + offset; break; case TYPE_TM1829: - return I_8266_U0_TM2_3 + offset; + t = I_8266_U0_TM2_3 + offset; break; case TYPE_UCS8903: - return I_8266_U0_UCS_3 + offset; + t = I_8266_U0_UCS_3 + offset; break; case TYPE_UCS8904: - return I_8266_U0_UCS_4 + offset; + t = I_8266_U0_UCS_4 + offset; break; case TYPE_APA106: - return I_8266_U0_APA106_3 + offset; + t = I_8266_U0_APA106_3 + offset; break; case TYPE_FW1906: - return I_8266_U0_FW6_5 + offset; + t = I_8266_U0_FW6_5 + offset; break; case TYPE_WS2805: - return I_8266_U0_2805_5 + offset; + t = I_8266_U0_2805_5 + offset; break; case TYPE_TM1914: - return I_8266_U0_TM1914_3 + offset; + t = I_8266_U0_TM1914_3 + offset; break; case TYPE_SM16825: - return I_8266_U0_SM16825_5 + offset; + t = I_8266_U0_SM16825_5 + offset; break; } #else //ESP32 - uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive] - #if defined(CONFIG_IDF_TARGET_ESP32S2) - // ESP32-S2 only has 4 RMT channels - if (_useParallelI2S) { - if (num > 11) return I_NONE; - if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT - // Note: conflicts with AudioReactive if enabled - } else { - if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive) - } - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - // On ESP32-C3 only the first 2 RMT channels are usable for transmitting - if (num > 1) return I_NONE; - //if (num > 1) offset = 1; // I2S not supported yet (only 1 I2S) - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - // On ESP32-S3 only the first 4 RMT channels are usable for transmitting - if (_useParallelI2S) { - if (num > 11) return I_NONE; - if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT - } else { - if (num > 3) return I_NONE; // do not use single I2S (as it is not supported) - } - #else - // standard ESP32 has 8 RMT and x1/x8 I2S1 channels - if (_useParallelI2S) { - if (num > 15) return I_NONE; - if (num < 8) offset = 1; // 8 I2S followed by 8 RMT + // dynamic channel allocation based on driver preference + // determine which driver to use based on preference and availability. First I2S bus locks the I2S type, all subsequent I2S buses are assigned the same type (hardware restriction) + uint8_t offset = 0; // 0 = RMT, 1 = I2S/LCD + if (driverPreference == 0 && _rmtChannelsAssigned < WLED_MAX_RMT_CHANNELS) { + _rmtChannelsAssigned++; + } else if (_i2sChannelsAssigned < WLED_MAX_I2S_CHANNELS) { + offset = 1; // I2S requested or RMT full + _i2sChannelsAssigned++; } else { - if (num > 9) return I_NONE; - if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + return I_NONE; // No channels available } - #endif + + // Now determine actual bus type with the chosen offset switch (busType) { case TYPE_WS2812_1CH_X3: case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: - return I_32_RN_NEO_3 + offset; + t = I_32_RN_NEO_3 + offset; break; case TYPE_SK6812_RGBW: - return I_32_RN_NEO_4 + offset; + t = I_32_RN_NEO_4 + offset; break; case TYPE_WS2811_400KHZ: - return I_32_RN_400_3 + offset; + t = I_32_RN_400_3 + offset; break; case TYPE_TM1814: - return I_32_RN_TM1_4 + offset; + t = I_32_RN_TM1_4 + offset; break; case TYPE_TM1829: - return I_32_RN_TM2_3 + offset; + t = I_32_RN_TM2_3 + offset; break; case TYPE_UCS8903: - return I_32_RN_UCS_3 + offset; + t = I_32_RN_UCS_3 + offset; break; case TYPE_UCS8904: - return I_32_RN_UCS_4 + offset; + t = I_32_RN_UCS_4 + offset; break; case TYPE_APA106: - return I_32_RN_APA106_3 + offset; + t = I_32_RN_APA106_3 + offset; break; case TYPE_FW1906: - return I_32_RN_FW6_5 + offset; + t = I_32_RN_FW6_5 + offset; break; case TYPE_WS2805: - return I_32_RN_2805_5 + offset; + t = I_32_RN_2805_5 + offset; break; case TYPE_TM1914: - return I_32_RN_TM1914_3 + offset; + t = I_32_RN_TM1914_3 + offset; break; case TYPE_SM16825: - return I_32_RN_SM16825_5 + offset; + t = I_32_RN_SM16825_5 + offset; break; + } + // If using parallel I2S, set the type accordingly + if (_i2sChannelsAssigned == 1 && offset == 1) { // first I2S channel request, lock the type + _parallelBusItype = t; + #ifdef CONFIG_IDF_TARGET_ESP32S3 + _useParallelI2S = true; // ESP32-S3 always uses parallel I2S (LCD method) + #endif + } + else if (offset == 1) { // not first I2S channel, use locked type and enable parallel flag + _useParallelI2S = true; + t = _parallelBusItype; } #endif } - return I_NONE; + return t; } }; #endif diff --git a/wled00/button.cpp b/wled00/button.cpp index f6a07f5107..d544dd73ab 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -367,24 +367,29 @@ void handleIO() // if we want to control on-board LED (ESP8266) or relay we have to do it here as the final show() may not happen until // next loop() cycle - if (strip.getBrightness()) { + handleOnOff(); +} + +void handleOnOff(bool forceOff) +{ + if (strip.getBrightness() && !forceOff) { lastOnTime = millis(); if (offMode) { BusManager::on(); if (rlyPin>=0) { - pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); - digitalWrite(rlyPin, rlyMde); - delay(50); // wait for relay to switch and power to stabilize + // note: pinMode is set in first call to handleOnOff(true) in beginStrip() + digitalWrite(rlyPin, rlyMde); // set to on state + delay(RELAY_DELAY); // let power stabilize before sending LED data (#346 #812 #3581 #3955) } offMode = false; } - } else if (millis() - lastOnTime > 600 && !strip.needsUpdate()) { + } else if ((millis() - lastOnTime > 600 && !strip.needsUpdate()) || forceOff) { // for turning LED or relay off we need to wait until strip no longer needs updates (strip.trigger()) if (!offMode) { BusManager::off(); if (rlyPin>=0) { + digitalWrite(rlyPin, !rlyMde); // set output before disabling high-z state to avoid output glitches pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); - digitalWrite(rlyPin, !rlyMde); } offMode = true; } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 28b63ea65c..4268c70a2f 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -38,7 +38,7 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy //simple macro for ArduinoJSON's or syntax #define CJSON(a,b) a = b | a -void getStringFromJson(char* dest, const char* src, size_t len) { +static inline void getStringFromJson(char* dest, const char* src, size_t len) { if (src != nullptr) strlcpy(dest, src, len); } @@ -107,6 +107,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { multiWiFi[n].staticIP = nIP; multiWiFi[n].staticGW = nGW; multiWiFi[n].staticSN = nSN; +#ifdef WLED_ENABLE_WPA_ENTERPRISE + byte encType = WIFI_ENCRYPTION_TYPE_PSK; + char anonIdent[65] = ""; + char ident[65] = ""; + CJSON(encType, wifi[F("enc_type")]); + getStringFromJson(anonIdent, wifi["e_anon_ident"], 65); + getStringFromJson(ident, wifi["e_ident"], 65); + multiWiFi[n].encryptionType = encType; + strlcpy(multiWiFi[n].enterpriseAnonIdentity, anonIdent, 65); + strlcpy(multiWiFi[n].enterpriseIdentity, ident, 65); +#endif if (++n >= WLED_MAX_WIFI_COUNT) break; } } @@ -165,10 +176,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(cctICused, hw_led[F("ic")]); uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); Bus::setCCTBlend(cctBlending); - strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - CJSON(useParallelI2S, hw_led[F("prl")]); - #endif + unsigned targetFPS = hw_led["fps"] | WLED_FPS; + strip.setTargetFps(targetFPS); //unlimited if 0, default 42 FPS #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -235,9 +244,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { maMax = 0; } ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh + uint8_t driverType = elm[F("drv")] | 0; // 0=RMT (default), 1=I2S note: polybus may override this if driver is not available String host = elm[F("text")] | String(); - busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host); + busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, driverType, host); doInitBusses = true; // finalization done in beginStrip() if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want } @@ -320,7 +330,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { unsigned start = 0; // analog always has length 1 if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; - busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); + busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, LED_MILLIAMPS_DEFAULT, ABL_MILLIAMPS_DEFAULT, 0); // driver=0 (RMT default) doInitBusses = true; // finalization done in beginStrip() } } @@ -507,13 +517,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.autoSegments, light[F("aseg")]); CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2 - float light_gc_bri = light["gc"]["bri"]; - float light_gc_col = light["gc"]["col"]; - if (light_gc_bri > 1.0f) gammaCorrectBri = true; - else gammaCorrectBri = false; - if (light_gc_col > 1.0f) gammaCorrectCol = true; - else gammaCorrectCol = false; - if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) { + float light_gc_bri = light["gc"]["bri"] | 1.0f; // default to 1.0 (false) + float light_gc_col = light["gc"]["col"] | gammaCorrectVal; // default to gammaCorrectVal (true) + if (light_gc_bri != 1.0f) gammaCorrectBri = true; + else gammaCorrectBri = false; + if (light_gc_col != 1.0f) gammaCorrectCol = true; + else gammaCorrectCol = false; + if (gammaCorrectVal < 0.1f || gammaCorrectVal > 3) { gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectBri = false; gammaCorrectCol = false; @@ -597,6 +607,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { tdd = if_live[F("timeout")] | -1; if (tdd >= 0) realtimeTimeoutMs = tdd * 100; +#ifdef WLED_ENABLE_DMX + CJSON(dmxOutputPin, if_live_dmx[F("dmxOutputPin")]); +#endif #ifdef WLED_ENABLE_DMX_INPUT CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]); CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]); @@ -791,7 +804,7 @@ void resetConfig() { bool deserializeConfigFromFS() { [[maybe_unused]] bool success = deserializeConfigSec(); - if (!requestJSONBufferLock(1)) return false; + if (!requestJSONBufferLock(JSON_LOCK_CFG_DES)) return false; DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); @@ -812,7 +825,7 @@ void serializeConfigToFS() { DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); - if (!requestJSONBufferLock(2)) return; + if (!requestJSONBufferLock(JSON_LOCK_CFG_SER)) return; JsonObject root = pDoc->to(); @@ -866,6 +879,13 @@ void serializeConfig(JsonObject root) { wifi_gw.add(multiWiFi[n].staticGW[i]); wifi_sn.add(multiWiFi[n].staticSN[i]); } +#ifdef WLED_ENABLE_WPA_ENTERPRISE + wifi[F("enc_type")] = multiWiFi[n].encryptionType; + if (multiWiFi[n].encryptionType == WIFI_ENCRYPTION_TYPE_ENTERPRISE) { + wifi[F("e_anon_ident")] = multiWiFi[n].enterpriseAnonIdentity; + wifi[F("e_ident")] = multiWiFi[n].enterpriseIdentity; + } +#endif } JsonArray dns = nw.createNestedArray(F("dns")); @@ -929,9 +949,6 @@ void serializeConfig(JsonObject root) { hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - hw_led[F("prl")] = BusManager::hasParallelOutput(); - #endif #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -958,7 +975,7 @@ void serializeConfig(JsonObject root) { for (size_t s = 0; s < BusManager::getNumBusses(); s++) { DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s); const Bus *bus = BusManager::getBus(s); - if (!bus || !bus->isOk()) break; + if (!bus) break; // Memory corruption, iterator invalid DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), (int)bus->getStart(), (int)(bus->getStart()+bus->getLength()), (int)(bus->getType() & 0x7F), @@ -985,6 +1002,7 @@ void serializeConfig(JsonObject root) { ins[F("freq")] = bus->getFrequency(); ins[F("maxpwr")] = bus->getMaxCurrent(); ins[F("ledma")] = bus->getLEDCurrent(); + ins[F("drv")] = bus->getDriverType(); ins[F("text")] = bus->getCustomText(); } @@ -1118,6 +1136,9 @@ void serializeConfig(JsonObject root) { if_live_dmx[F("addr")] = DMXAddress; if_live_dmx[F("dss")] = DMXSegmentSpacing; if_live_dmx["mode"] = DMXMode; + #ifdef WLED_ENABLE_DMX + if_live_dmx[F("dmxOutputPin")] = dmxOutputPin; + #endif #ifdef WLED_ENABLE_DMX_INPUT if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin; if_live_dmx[F("inputTxPin")] = dmxInputReceivePin; @@ -1256,7 +1277,7 @@ static const char s_wsec_json[] PROGMEM = "/wsec.json"; bool deserializeConfigSec() { DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); - if (!requestJSONBufferLock(3)) return false; + if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_DES)) return false; bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc); if (!success) { @@ -1310,7 +1331,7 @@ bool deserializeConfigSec() { void serializeConfigSec() { DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); - if (!requestJSONBufferLock(4)) return; + if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_SER)) return; JsonObject root = pDoc->to(); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index bea7e61728..8ecde0820b 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -249,12 +249,13 @@ void loadCustomPalettes() { byte tcp[72]; //support gradient palettes with up to 18 entries CRGBPalette16 targetPalette; customPalettes.clear(); // start fresh + StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers -> TODO: current format uses 214 bytes max per palette, why is this buffer so large? + unsigned emptyPaletteGap = 0; // count gaps in palette files to stop looking for more (each exists() call takes ~5ms) for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) { char fileName[32]; sprintf_P(fileName, PSTR("/palette%d.json"), index); - - StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers if (WLED_FS.exists(fileName)) { + emptyPaletteGap = 0; // reset gap counter if file exists DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName); if (readObjectFromFile(fileName, nullptr, &pDoc)) { JsonArray pal = pDoc[F("palette")]; @@ -288,7 +289,8 @@ void loadCustomPalettes() { } } } else { - break; + emptyPaletteGap++; + if (emptyPaletteGap > WLED_MAX_CUSTOM_PALETTE_GAP) break; // stop looking for more palettes } } } diff --git a/wled00/const.h b/wled00/const.h index 6d1825d574..aac2c232a6 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -15,6 +15,7 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C #else #define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10 #endif +#define WLED_MAX_CUSTOM_PALETTE_GAP 20 // max number of empty palette files in a row before stopping to look for more (20 takes 100ms) // You can define custom product info from build flags. // This is useful to allow API consumer to identify what type of WLED version @@ -55,32 +56,37 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C #ifdef ESP8266 #define WLED_MAX_DIGITAL_CHANNELS 3 + #define WLED_MAX_RMT_CHANNELS 0 // ESP8266 does not have RMT nor I2S + #define WLED_MAX_I2S_CHANNELS 0 #define WLED_MAX_ANALOG_CHANNELS 5 - #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_PLATFORM_ID 0 // used in UI to distinguish ESP types, needs a proper fix! #else #if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX) #include "driver/ledc.h" // needed for analog/LEDC channel counts #endif #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_DIGITAL_CHANNELS 2 + #define WLED_MAX_RMT_CHANNELS 2 // ESP32-C3 has 2 RMT output channels + #define WLED_MAX_I2S_CHANNELS 0 // I2S not supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 6 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_PLATFORM_ID 1 // used in UI to distinguish ESP types, needs a proper fix! #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB - // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0 + #define WLED_MAX_RMT_CHANNELS 4 // ESP32-S2 has 4 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_PLATFORM_ID 2 // used in UI to distinguish ESP type in UI #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1 - #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD + #define WLED_MAX_RMT_CHANNELS 4 // ESP32-S3 has 4 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // uses LCD parallel output not I2S //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_PLATFORM_ID 3 // used in UI to distinguish ESP type in UI, needs a proper fix! #else - // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT + #define WLED_MAX_RMT_CHANNELS 8 // ESP32 has 8 RMT output channels + #define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB //#define WLED_MAX_ANALOG_CHANNELS 16 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI + #define WLED_PLATFORM_ID 4 // used in UI to distinguish ESP type in UI, needs a proper fix! #endif + #define WLED_MAX_DIGITAL_CHANNELS (WLED_MAX_RMT_CHANNELS + WLED_MAX_I2S_CHANNELS) #endif // WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed // instead it will help determine max number of buses that can be defined at compile time @@ -113,6 +119,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #endif #endif +#define RELAY_DELAY 50 // delay in ms between switching on relay and sending data to LEDs + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2) #define WLED_MAX_COLOR_ORDER_MAPPINGS 5 #else @@ -208,6 +216,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define USERMOD_ID_BRIGHTNESS_FOLLOW_SUN 57 //Usermod "usermod_v2_brightness_follow_sun.h" #define USERMOD_ID_USER_FX 58 //Usermod "user_fx" +//Wifi encryption type +#ifdef WLED_ENABLE_WPA_ENTERPRISE + #define WIFI_ENCRYPTION_TYPE_PSK 0 //None/WPA/WPA2 + #define WIFI_ENCRYPTION_TYPE_ENTERPRISE 1 //WPA/WPA2-Enterprise +#endif + //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost) @@ -299,7 +313,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define TYPE_UCS8903 26 #define TYPE_APA106 27 #define TYPE_FW1906 28 //RGB + CW + WW + unused channel (6 channels per IC) -#define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) +#define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 #define TYPE_WS2805 32 //RGB + WW + CW @@ -441,6 +455,31 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented) #define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented) +// JSON buffer lock owners +#define JSON_LOCK_UNKNOWN 255 +#define JSON_LOCK_CFG_DES 1 +#define JSON_LOCK_CFG_SER 2 +#define JSON_LOCK_CFG_SEC_DES 3 +#define JSON_LOCK_CFG_SEC_SER 4 +#define JSON_LOCK_SETTINGS 5 +#define JSON_LOCK_XML 6 +#define JSON_LOCK_LEDMAP 7 +// unused 8 +#define JSON_LOCK_PRESET_LOAD 9 +#define JSON_LOCK_PRESET_SAVE 10 +#define JSON_LOCK_WS_RECEIVE 11 +#define JSON_LOCK_WS_SEND 12 +#define JSON_LOCK_IR 13 +#define JSON_LOCK_SERVER 14 +#define JSON_LOCK_MQTT 15 +#define JSON_LOCK_SERIAL 16 +#define JSON_LOCK_SERVEJSON 17 +#define JSON_LOCK_NOTIFY 18 +#define JSON_LOCK_PRESET_NAME 19 +#define JSON_LOCK_LEDGAP 20 +#define JSON_LOCK_LEDMAP_ENUM 21 +#define JSON_LOCK_REMOTE 22 + // Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness #define NL_MODE_FADE 1 //Fade to target brightness gradually @@ -459,6 +498,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define SUBPAGE_UM 8 #define SUBPAGE_UPDATE 9 #define SUBPAGE_2D 10 +#define SUBPAGE_PINS 11 +#define SUBPAGE_LAST SUBPAGE_PINS #define SUBPAGE_LOCK 251 #define SUBPAGE_PINREQ 252 #define SUBPAGE_CSS 253 @@ -474,23 +515,28 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs #elif defined(CONFIG_IDF_TARGET_ESP32S2) #define MAX_LEDS 2048 //due to memory constraints S2 - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - #define MAX_LEDS 4096 #else #define MAX_LEDS 16384 #endif #endif +// maximum total memory that can be used for bus-buffers and pixel buffers #ifndef MAX_LED_MEMORY #ifdef ESP8266 - #define MAX_LED_MEMORY 4096 + #define MAX_LED_MEMORY (8*1024) #else - #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_LED_MEMORY 16384 - #elif defined(ARDUINO_ARCH_ESP32C3) - #define MAX_LED_MEMORY 32768 + #if defined(CONFIG_IDF_TARGET_ESP32S2) + #ifndef BOARD_HAS_PSRAM + #define MAX_LED_MEMORY (28*1024) // S2 has ~170k of free heap after boot, using 28k is the absolute limit to keep WLED functional + #else + #define MAX_LED_MEMORY (48*1024) // with PSRAM there is more wiggle room as buffers get moved to PSRAM when needed (prioritize functionality over speed) + #endif + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_LED_MEMORY (192*1024) // S3 has ~330k of free heap after boot + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + #define MAX_LED_MEMORY (100*1024) // C3 has ~240k of free heap after boot, even with 8000 LEDs configured (2D) there is 30k of contiguous heap left #else - #define MAX_LED_MEMORY 65536 + #define MAX_LED_MEMORY (85*1024) // ESP32 has ~160k of free heap after boot and an additional 64k of 32bit access memory that is used for pixel buffers #endif #endif #endif @@ -555,7 +601,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #ifdef ESP8266 #define JSON_BUFFER_SIZE 10240 #else - #if defined(ARDUINO_ARCH_ESP32S2) + #if defined(CONFIG_IDF_TARGET_ESP32S2) #define JSON_BUFFER_SIZE 24576 #else #define JSON_BUFFER_SIZE 32767 @@ -604,14 +650,14 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); // Defaults pins, type and counts to configure LED output #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) - #ifdef WLED_ENABLE_DMX - #define DEFAULT_LED_PIN 1 - #warning "Compiling with DMX. The default LED pin has been changed to pin 1." - #else #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board + #else + #if defined(WLED_USE_ETHERNET) + #define DEFAULT_LED_PIN 4 // GPIO4 seems to be a "safe bet" for all known ethernet boards (issue #5155) + //#warning "Compiling with Ethernet support. The default LED pin has been changed to pin 4." + #else + #define DEFAULT_LED_PIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif -#else - #define DEFAULT_LED_PIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif #define DEFAULT_LED_TYPE TYPE_WS2812_RGB #define DEFAULT_LED_COUNT 30 diff --git a/wled00/data/404.htm b/wled00/data/404.htm index ff41fa6e03..caf6856def 100644 --- a/wled00/data/404.htm +++ b/wled00/data/404.htm @@ -1,5 +1,5 @@ - + diff --git a/wled00/data/common.js b/wled00/data/common.js index 5f73c946d8..a6223daa7c 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -137,16 +137,26 @@ function showToast(text, error = false) { x.style.animation = 'none'; timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); } -function uploadFile(fileObj, name) { +async function uploadFile(fileObj, name, callback) { + let file = fileObj.files?.[0]; // get first file, "?"" = optional chaining in case no file is selected + if (!file) { callback?.(false); return; } + if (/\.json$/i.test(name)) { // same as name.toLowerCase().endsWith('.json') + try { + const minified = JSON.stringify(JSON.parse(await file.text())); // validate and minify JSON + file = new Blob([minified], { type: file.type || "application/json" }); + } catch (err) { + if (!confirm("JSON invalid. Continue?")) { callback?.(false); return; } + // proceed with original file if invalid but user confirms + } + } var req = new XMLHttpRequest(); - req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); - req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400); if(callback) callback(this.status < 400);}); + req.addEventListener('error', function(e){showToast("Upload failed",true); if(callback) callback(false);}); req.open("POST", "/upload"); var formData = new FormData(); - formData.append("data", fileObj.files[0], name); + formData.append("data", file, name); req.send(formData); fileObj.value = ''; - return false; } // connect to WebSocket, use parent WS or open new, callback function gets passed the new WS object function connectWs(onOpen) { diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b8e0e08be1..4e41757a94 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -1,646 +1,895 @@ - - - - - - - WLED Custom Palette Editor - - - - - + + + + WLED Palette Editor + + -
-
-

- - - - - - - WLED Palette Editor -

-
- -
-
-
-
- -
Custom palettes
-
-
- -
-
Click gradient to add. Box = color. Red = delete. Arrow = upload. Pencil = edit.
-
-
-
-
Static palettes
-
-
+
+

WLED Palette Editor

+ +
+
+
+ + + +
+
+ +
+
+ + +
+
+ + + +
+
+ +
+
+
+ + + +
+ +
+ Warning: Adding many custom palettes might cause stability issues, create backups +
+
+ +
+ +
+ +
+
+ +
+ +
by @dedehai
+
+ + + - - - + \ No newline at end of file diff --git a/wled00/data/dmxmap.htm b/wled00/data/dmxmap.htm index 25953b0e37..2b821c1e77 100644 --- a/wled00/data/dmxmap.htm +++ b/wled00/data/dmxmap.htm @@ -1,5 +1,5 @@ - + DMX Map - - - - +
diff --git a/wled00/data/icons-ui/HowTo_AddNewIcons.txt b/wled00/data/icons-ui/HowTo_AddNewIcons.txt new file mode 100644 index 0000000000..beefb288c8 --- /dev/null +++ b/wled00/data/icons-ui/HowTo_AddNewIcons.txt @@ -0,0 +1,14 @@ +To edit the current font, this is the workflow: + +go to https://icomoon.io/ +In the menu, go to manage projects and import the json file from this folder and load it +Add new icons or exchange existing ones: if changing existing one, make sure the unicode stays the same (can be edited before exporting) +Go to "Generate SVG & More" and check the size of new icons (clicking on icons brings up the editor) -> scale new icons to match the size of existing ones +Go to "Generate font" tab, check unicodes are correct (can use any unicode, range > e900 is "custom range" and now preferred) +Download the font package and replace the files in this folder with new files +Using an online converter, convert the *.woff font into woff2 format (about half the file size) +Using another online converter, convert the woff2 font to base64 encoding for CSS +in index.css, replace the font string at the top, keep the "data:font/woff2;charset=utf-8;" and dont use octet-stream (browser compatibility). + +enjoy your new icons in the UI :) + diff --git a/wled00/data/icons-ui/Read Me.txt b/wled00/data/icons-ui/Read Me.txt index 8491652f88..6fceb3881b 100644 --- a/wled00/data/icons-ui/Read Me.txt +++ b/wled00/data/icons-ui/Read Me.txt @@ -1,6 +1,6 @@ Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. -To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts +To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/docs#install You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. diff --git a/wled00/data/icons-ui/demo-files/demo.css b/wled00/data/icons-ui/demo-files/demo.css index 39b8991da7..731f7b29b6 100644 --- a/wled00/data/icons-ui/demo-files/demo.css +++ b/wled00/data/icons-ui/demo-files/demo.css @@ -147,6 +147,12 @@ p { font-size: 16px; } .fs1 { + font-size: 48px; +} +.fs2 { + font-size: 28px; +} +.fs3 { font-size: 32px; } diff --git a/wled00/data/icons-ui/demo.html b/wled00/data/icons-ui/demo.html index 0416231fb0..c5d596e966 100644 --- a/wled00/data/icons-ui/demo.html +++ b/wled00/data/icons-ui/demo.html @@ -9,11 +9,45 @@
-

Font Name: wled122 (Glyphs: 23)

+

Font Name: wled122 (Glyphs: 25)

-

Grid Size: Unknown

+

Grid Size: 16

+
+ + i-pixelforge +
+
+ + +
+
+ liga: + +
+
+
+
+

Grid Size: 14

+
+
+ + i-editor +
+
+ + +
+
+ liga: + +
+
+
+
+

Grid Size: Unknown

+
i-pattern @@ -27,7 +61,7 @@

Grid Size: Unknown

-
+
i-segments @@ -41,7 +75,7 @@

Grid Size: Unknown

-
+
i-sun @@ -55,7 +89,7 @@

Grid Size: Unknown

-
+
i-palette @@ -69,7 +103,7 @@

Grid Size: Unknown

-
+
i-eye @@ -83,7 +117,7 @@

Grid Size: Unknown

-
+
i-speed @@ -97,7 +131,7 @@

Grid Size: Unknown

-
+
i-expand @@ -111,7 +145,7 @@

Grid Size: Unknown

-
+
i-power @@ -125,7 +159,7 @@

Grid Size: Unknown

-
+
i-settings @@ -139,7 +173,7 @@

Grid Size: Unknown

-
+
i-playlist @@ -153,7 +187,7 @@

Grid Size: Unknown

-
+
i-night @@ -167,7 +201,7 @@

Grid Size: Unknown

-
+
i-cancel @@ -181,7 +215,7 @@

Grid Size: Unknown

-
+
i-sync @@ -195,7 +229,7 @@

Grid Size: Unknown

-
+
i-confirm @@ -209,7 +243,7 @@

Grid Size: Unknown

-
+
i-brightness @@ -223,7 +257,7 @@

Grid Size: Unknown

-
+
i-nodes @@ -237,7 +271,7 @@

Grid Size: Unknown

-
+
i-add @@ -251,7 +285,7 @@

Grid Size: Unknown

-
+
i-edit @@ -265,7 +299,7 @@

Grid Size: Unknown

-
+
i-intensity @@ -279,7 +313,7 @@

Grid Size: Unknown

-
+
i-star @@ -293,7 +327,7 @@

Grid Size: Unknown

-
+
i-info @@ -307,7 +341,7 @@

Grid Size: Unknown

-
+
i-del @@ -321,7 +355,7 @@

Grid Size: Unknown

-
+
i-presets diff --git a/wled00/data/icons-ui/fonts/wled122.svg b/wled00/data/icons-ui/fonts/wled122.svg new file mode 100644 index 0000000000..082edf54d3 --- /dev/null +++ b/wled00/data/icons-ui/fonts/wled122.svg @@ -0,0 +1,35 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wled00/data/icons-ui/fonts/wled122.ttf b/wled00/data/icons-ui/fonts/wled122.ttf new file mode 100644 index 0000000000..84f7719585 Binary files /dev/null and b/wled00/data/icons-ui/fonts/wled122.ttf differ diff --git a/wled00/data/icons-ui/fonts/wled122.woff b/wled00/data/icons-ui/fonts/wled122.woff index cc389b3a5a..2fdda4df7d 100644 Binary files a/wled00/data/icons-ui/fonts/wled122.woff and b/wled00/data/icons-ui/fonts/wled122.woff differ diff --git a/wled00/data/icons-ui/fonts/wled122.woff2 b/wled00/data/icons-ui/fonts/wled122.woff2 index d13b37ac0c..f049a31aff 100644 Binary files a/wled00/data/icons-ui/fonts/wled122.woff2 and b/wled00/data/icons-ui/fonts/wled122.woff2 differ diff --git a/wled00/data/icons-ui/selection.json b/wled00/data/icons-ui/selection.json index 5af8516b8b..b2c2b14a91 100644 --- a/wled00/data/icons-ui/selection.json +++ b/wled00/data/icons-ui/selection.json @@ -1 +1 @@ -{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M511.573 85.333c235.947 0 427.094 191.147 427.094 426.667s-191.147 426.667-427.094 426.667c-235.52 0-426.24-191.147-426.24-426.667s190.72-426.667 426.24-426.667zM512 853.333c188.587 0 341.333-152.746 341.333-341.333s-152.746-341.333-341.333-341.333-341.333 152.746-341.333 341.333 152.746 341.333 341.333 341.333zM661.333 469.333c-35.413 0-64-28.586-64-64 0-35.413 28.587-64 64-64 35.414 0 64 28.587 64 64 0 35.414-28.586 64-64 64zM362.667 469.333c-35.414 0-64-28.586-64-64 0-35.413 28.586-64 64-64 35.413 0 64 28.587 64 64 0 35.414-28.587 64-64 64zM512 746.667c-99.413 0-183.893-62.294-218.027-149.334h436.054c-34.134 87.040-118.614 149.334-218.027 149.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE23D"],"defaultCode":57917,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":11,"order":26,"prevSize":32,"code":57917,"name":"pattern"},"setIdx":0,"setId":0,"iconIdx":10},{"icon":{"paths":["M511.573 791.040l314.88-244.907 69.547 54.187-384 298.667-384-298.667 69.12-53.76zM512 682.667l-384-298.667 384-298.667 384 298.667-69.973 54.187z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE34B"],"defaultCode":58187,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":14,"order":35,"ligatures":"","prevSize":32,"code":58187,"name":"segments"},"setIdx":0,"setId":0,"iconIdx":13},{"icon":{"paths":["M288.427 206.507l-60.587 60.16-76.373-76.374 60.16-60.16zM170.667 448v85.333h-128v-85.333h128zM554.667 23.467v125.866h-85.334v-125.866h85.334zM872.533 190.293l-76.373 76.374-60.16-60.16 76.373-76.374zM735.573 774.827l59.734-59.734 76.8 76.374-60.16 60.16zM853.333 448h128v85.333h-128v-85.333zM512 234.667c141.227 0 256 114.773 256 256 0 141.226-114.773 256-256 256s-256-114.774-256-256c0-141.227 114.773-256 256-256zM469.333 957.867v-125.867h85.334v125.867h-85.334zM151.467 791.040l76.373-76.8 60.16 60.16-76.373 76.8z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE333"],"defaultCode":58163,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":40,"order":73,"ligatures":"","prevSize":32,"code":58163,"name":"sun"},"setIdx":0,"setId":0,"iconIdx":39},{"icon":{"paths":["M512 128c212.053 0 384 152.747 384 341.333 0 117.76-95.573 213.334-213.333 213.334h-75.52c-35.414 0-64 28.586-64 64 0 16.213 6.4 31.146 16.213 42.24 10.24 11.52 16.64 26.453 16.64 43.093 0 35.413-28.587 64-64 64-212.053 0-384-171.947-384-384s171.947-384 384-384zM277.333 512c35.414 0 64-28.587 64-64s-28.586-64-64-64c-35.413 0-64 28.587-64 64s28.587 64 64 64zM405.333 341.333c35.414 0 64-28.586 64-64 0-35.413-28.586-64-64-64-35.413 0-64 28.587-64 64 0 35.414 28.587 64 64 64zM618.667 341.333c35.413 0 64-28.586 64-64 0-35.413-28.587-64-64-64-35.414 0-64 28.587-64 64 0 35.414 28.586 64 64 64zM746.667 512c35.413 0 64-28.587 64-64s-28.587-64-64-64c-35.414 0-64 28.587-64 64s28.586 64 64 64z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2B3"],"defaultCode":58035,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":75,"order":22,"prevSize":32,"code":58035,"name":"palette"},"setIdx":0,"setId":0,"iconIdx":74},{"icon":{"paths":["M512 192c213.333 0 395.52 132.693 469.333 320-73.813 187.307-256 320-469.333 320s-395.52-132.693-469.333-320c73.813-187.307 256-320 469.333-320zM512 725.333c117.76 0 213.333-95.573 213.333-213.333s-95.573-213.333-213.333-213.333-213.333 95.573-213.333 213.333 95.573 213.333 213.333 213.333zM512 384c70.827 0 128 57.173 128 128s-57.173 128-128 128-128-57.173-128-128 57.173-128 128-128z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE0E8"],"defaultCode":57576,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":172,"order":74,"prevSize":32,"code":57576,"name":"eye"},"setIdx":0,"setId":0,"iconIdx":171},{"icon":{"paths":["M640 42.667v85.333h-256v-85.333h256zM469.333 597.333v-256h85.334v256h-85.334zM811.947 315.307c52.48 65.706 84.053 148.906 84.053 239.36 0 212.053-171.52 384-384 384s-384-171.947-384-384c0-212.054 171.947-384 384-384 90.453 0 173.653 31.573 239.787 84.48l60.586-60.587c21.76 17.92 41.814 38.4 60.16 60.16zM512 853.333c165.12 0 298.667-133.546 298.667-298.666s-133.547-298.667-298.667-298.667-298.667 133.547-298.667 298.667 133.547 298.666 298.667 298.666z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE325"],"defaultCode":58149,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":370,"order":21,"ligatures":"","prevSize":32,"code":58149,"name":"speed"},"setIdx":0,"setId":0,"iconIdx":369},{"icon":{"paths":["M707.84 366.507l60.16 60.16-256 256-256-256 60.16-60.16 195.84 195.413z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE395"],"defaultCode":58261,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":549,"order":69,"ligatures":"","prevSize":32,"code":58261,"name":"expand"},"setIdx":0,"setId":0,"iconIdx":548},{"icon":{"paths":["M554.667 128v426.667h-85.334v-426.667h85.334zM760.747 220.587c82.773 70.4 135.253 174.506 135.253 291.413 0 212.053-171.947 384-384 384s-384-171.947-384-384c0-116.907 52.48-221.013 135.253-291.413l60.16 60.16c-66.986 54.613-110.080 137.813-110.080 231.253 0 165.12 133.547 298.667 298.667 298.667s298.667-133.547 298.667-298.667c0-93.44-43.094-176.64-110.507-230.827z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE08F"],"defaultCode":57487,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":557,"order":19,"ligatures":"","prevSize":32,"code":57487,"name":"power"},"setIdx":0,"setId":0,"iconIdx":556},{"icon":{"paths":["M816.64 551.936l85.504 67.584c8.192 6.144 10.24 16.896 5.12 26.112l-81.92 141.824c-5.12 9.216-15.872 12.8-25.088 9.216l-101.888-40.96c-20.992 15.872-44.032 29.696-69.12 39.936l-15.36 108.544c-1.024 10.24-9.728 17.408-19.968 17.408h-163.84c-10.24 0-18.432-7.168-20.48-17.408l-15.36-108.544c-25.088-10.24-47.616-23.552-69.12-39.936l-101.888 40.96c-9.216 3.072-19.968 0-25.088-9.216l-81.92-141.824c-4.608-8.704-2.56-19.968 5.12-26.112l86.528-67.584c-2.048-12.8-3.072-26.624-3.072-39.936s1.536-27.136 3.584-39.936l-86.528-67.584c-8.192-6.144-10.24-16.896-5.12-26.112l81.92-141.824c5.12-9.216 15.872-12.8 25.088-9.216l101.888 40.96c20.992-15.872 44.032-29.696 69.12-39.936l15.36-108.544c1.536-10.24 9.728-17.408 19.968-17.408h163.84c10.24 0 18.944 7.168 20.48 17.408l15.36 108.544c25.088 10.24 47.616 23.552 69.12 39.936l101.888-40.96c9.216-3.072 19.968 0 25.088 9.216l81.92 141.824c4.608 8.704 2.56 19.968-5.12 26.112l-86.528 67.584c2.048 12.8 3.072 26.112 3.072 39.936s-1.024 27.136-2.56 39.936zM512 665.6c84.48 0 153.6-69.12 153.6-153.6s-69.12-153.6-153.6-153.6-153.6 69.12-153.6 153.6 69.12 153.6 153.6 153.6z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE0A2"],"defaultCode":57506,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":562,"order":29,"ligatures":"","prevSize":32,"code":57506,"name":"settings"},"setIdx":0,"setId":0,"iconIdx":561},{"icon":{"paths":["M556.8 417.707l125.867 94.293-256 192v-384zM556.8 417.707l125.867 94.293-256 192v-384zM556.8 417.707l-130.133-97.707v384l256-192zM469.333 173.653c-62.293 7.68-119.040 32.427-166.4 69.12l-60.586-61.013c63.146-51.627 141.226-85.76 226.986-94.293v86.186zM242.773 302.933c-36.693 47.36-61.44 104.107-69.12 166.4h-86.186c8.533-85.76 42.666-163.84 94.293-226.986zM173.653 554.667c7.68 62.293 32.427 119.040 69.12 165.973l-61.013 61.013c-51.627-63.146-85.76-141.226-94.293-226.986h86.186zM242.347 842.24l60.586-61.013c47.36 36.693 104.107 61.44 166.4 69.12v86.186c-85.333-8.533-163.84-42.666-226.986-94.293zM938.667 512c0 220.16-167.254 401.92-381.867 424.533v-86.186c167.253-22.187 296.533-165.547 296.533-338.347s-129.28-316.16-296.533-338.347v-86.186c214.613 22.613 381.867 204.373 381.867 424.533z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE139"],"defaultCode":57657,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":595,"order":46,"ligatures":"","prevSize":32,"code":57657,"name":"playlist"},"setIdx":0,"setId":0,"iconIdx":594},{"icon":{"paths":["M384 85.333c235.52 0 426.667 191.147 426.667 426.667s-191.147 426.667-426.667 426.667c-44.8 0-87.467-6.827-128-19.627 173.227-54.187 298.667-215.893 298.667-407.040s-125.44-352.853-298.667-407.040c40.533-12.8 83.2-19.627 128-19.627z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2A2"],"defaultCode":58018,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":607,"order":34,"ligatures":"","prevSize":32,"code":58018,"name":"night"},"setIdx":0,"setId":0,"iconIdx":606},{"icon":{"paths":["M512 85.333c235.947 0 426.667 190.72 426.667 426.667s-190.72 426.667-426.667 426.667-426.667-190.72-426.667-426.667 190.72-426.667 426.667-426.667zM725.333 665.173l-153.173-153.173 153.173-153.173-60.16-60.16-153.173 153.173-153.173-153.173-60.16 60.16 153.173 153.173-153.173 153.173 60.16 60.16 153.173-153.173 153.173 153.173z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE38F"],"defaultCode":58255,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":662,"order":50,"ligatures":"","prevSize":32,"code":58255,"name":"cancel"},"setIdx":0,"setId":0,"iconIdx":661},{"icon":{"paths":["M512 170.667c188.587 0 341.333 152.746 341.333 341.333 0 66.987-19.626 129.28-52.906 181.76l-62.294-62.293c19.2-35.414 29.867-76.374 29.867-119.467 0-141.227-114.773-256-256-256v128l-170.667-170.667 170.667-170.666v128zM512 768v-128l170.667 170.667-170.667 170.666v-128c-188.587 0-341.333-152.746-341.333-341.333 0-66.987 19.626-129.28 52.906-181.76l62.294 62.293c-19.2 35.414-29.867 76.374-29.867 119.467 0 141.227 114.773 256 256 256z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE116"],"defaultCode":57622,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":709,"order":17,"ligatures":"","prevSize":32,"code":57622,"name":"sync"},"setIdx":0,"setId":0,"iconIdx":708},{"icon":{"paths":["M384 689.92l451.84-451.413 60.16 60.16-512 512-238.507-238.507 60.587-60.16z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE390"],"defaultCode":58256,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":733,"order":56,"ligatures":"","prevSize":32,"code":58256,"name":"confirm"},"setIdx":0,"setId":0,"iconIdx":732},{"icon":{"paths":["M853.333 370.773l141.227 141.227-141.227 141.227v200.106h-200.106l-141.227 141.227-141.227-141.227h-200.106v-200.106l-141.227-141.227 141.227-141.227v-200.106h200.106l141.227-141.227 141.227 141.227h200.106v200.106zM512 768c141.227 0 256-114.773 256-256s-114.773-256-256-256-256 114.773-256 256 114.773 256 256 256zM512 341.333c94.293 0 170.667 76.374 170.667 170.667s-76.374 170.667-170.667 170.667-170.667-76.374-170.667-170.667 76.374-170.667 170.667-170.667z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2A6"],"defaultCode":58022,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":785,"order":15,"ligatures":"","prevSize":32,"code":58022,"name":"brightness"},"setIdx":0,"setId":0,"iconIdx":784},{"icon":{"paths":["M85.333 725.333v-42.666h128v170.666h-128v-42.666h85.334v-21.334h-42.667v-42.666h42.667v-21.334h-85.334zM128 341.333v-128h-42.667v-42.666h85.334v170.666h-42.667zM85.333 469.333v-42.666h128v38.4l-76.8 89.6h76.8v42.666h-128v-38.4l76.8-89.6h-76.8zM298.667 213.333h597.333v85.334h-597.333v-85.334zM298.667 810.667v-85.334h597.333v85.334h-597.333zM298.667 554.667v-85.334h597.333v85.334h-597.333z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE22D"],"defaultCode":57901,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":797,"order":58,"ligatures":"","prevSize":32,"code":57901,"name":"nodes"},"setIdx":0,"setId":0,"iconIdx":796},{"icon":{"paths":["M810.667 554.667h-256v256h-85.334v-256h-256v-85.334h256v-256h85.334v256h256v85.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE18A"],"defaultCode":57738,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":803,"order":59,"ligatures":"","prevSize":32,"code":57738,"name":"add"},"setIdx":0,"setId":0,"iconIdx":802},{"icon":{"paths":["M128 736l471.893-471.893 160 160-471.893 471.893h-160v-160zM883.627 300.373l-78.080 78.080-160-160 78.080-78.080c16.64-16.64 43.52-16.64 60.16 0l99.84 99.84c16.64 16.64 16.64 43.52 0 60.16z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2C6"],"defaultCode":58054,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":834,"order":72,"ligatures":"","prevSize":32,"code":58054,"name":"edit"},"setIdx":0,"setId":0,"iconIdx":833},{"icon":{"paths":["M576 28.587c166.827 133.546 277.333 338.773 277.333 568.746 0 188.587-152.746 341.334-341.333 341.334s-341.333-152.747-341.333-341.334c0-144.213 51.626-276.906 137.813-379.306l-1.28 15.36c0 87.893 66.56 159.146 154.88 159.146 87.893 0 145.493-71.253 145.493-159.146 0-91.734-31.573-204.8-31.573-204.8zM499.627 810.667c113.066 0 204.8-91.734 204.8-204.8 0-59.307-8.534-117.334-25.174-172.374-43.52 58.454-121.6 94.72-197.12 110.080-75.093 15.36-119.893 64-119.893 133.12 0 74.24 61.44 133.974 137.387 133.974z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE409"],"defaultCode":58377,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":871,"order":10,"ligatures":"","prevSize":32,"code":58377,"name":"intensity"},"setIdx":0,"setId":0,"iconIdx":870},{"icon":{"paths":["M938.667 394.24l-232.534 201.813 69.547 299.947-263.68-159.147-263.68 159.147 69.973-299.947-232.96-201.813 306.774-26.027 119.893-282.88 119.893 282.454zM512 657.067l160.853 97.28-42.666-182.614 141.653-122.88-186.88-16.213-72.96-172.373-72.533 171.946-186.88 16.214 141.653 122.88-42.667 182.613z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE410"],"defaultCode":58384,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":927,"order":9,"ligatures":"","prevSize":32,"code":58384,"name":"star"},"setIdx":0,"setId":0,"iconIdx":926},{"icon":{"paths":["M512 85.333c235.52 0 426.667 191.147 426.667 426.667s-191.147 426.667-426.667 426.667-426.667-191.147-426.667-426.667 191.147-426.667 426.667-426.667zM554.667 725.333v-256h-85.334v256h85.334zM554.667 384v-85.333h-85.334v85.333h85.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE066"],"defaultCode":57446,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":952,"order":62,"ligatures":"","prevSize":32,"code":57446,"name":"info"},"setIdx":0,"setId":0,"iconIdx":951},{"icon":{"paths":["M256 810.667v-512h512v512c0 46.933-38.4 85.333-85.333 85.333h-341.334c-46.933 0-85.333-38.4-85.333-85.333zM810.667 170.667v85.333h-597.334v-85.333h149.334l42.666-42.667h213.334l42.666 42.667h149.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE037"],"defaultCode":57399,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":969,"order":55,"ligatures":"","prevSize":32,"code":57399,"name":"del"},"setIdx":0,"setId":0,"iconIdx":968},{"icon":{"paths":["M704 128c131.413 0 234.667 103.253 234.667 234.667 0 161.28-145.067 292.693-364.8 491.946l-61.867 56.32-61.867-55.893c-219.733-199.68-364.8-331.093-364.8-492.373 0-131.414 103.254-234.667 234.667-234.667 74.24 0 145.493 34.56 192 89.173 46.507-54.613 117.76-89.173 192-89.173zM516.267 791.467c203.093-183.894 337.066-305.494 337.066-428.8 0-85.334-64-149.334-149.333-149.334-65.707 0-129.707 42.24-151.893 100.694h-79.787c-22.613-58.454-86.613-100.694-152.32-100.694-85.333 0-149.333 64-149.333 149.334 0 123.306 133.973 244.906 337.066 428.8l4.267 4.266z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE04C"],"defaultCode":57420,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":1034,"order":61,"ligatures":"","prevSize":32,"code":57420,"name":"presets"},"setIdx":0,"setId":0,"iconIdx":1033}],"height":1024,"metadata":{"name":"wled122"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"i-","metadata":{"fontFamily":"wled122","majorVersion":1,"minorVersion":7},"metrics":{"emSize":1024,"baseline":20,"whitespace":0},"embed":false,"autoHost":true,"noie8":true,"ie7":false,"showSelector":false,"showMetrics":false,"showMetadata":false,"showVersion":true},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215},"historySize":50,"quickUsageToken":{"MainUI":false},"showLiga":false}} \ No newline at end of file +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M910.398 765.581l-241.236-241.236c-14.934-14.934-39.371-14.934-54.306 0l-18.102 18.102-147.2-147.2 241.646-241.648h-256.001l-113.645 113.645-11.249-11.247h-54.306v54.306l11.247 11.247-164.848 164.849 127.999 127.999 164.848-164.848 147.2 147.2-18.102 18.102c-14.934 14.934-14.934 39.371 0 54.306l241.236 241.236c14.934 14.934 39.371 14.934 54.306 0l90.509-90.509c14.935-14.934 14.935-39.371 0.002-54.306z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["hammer","tool","fix","make","generate","work","build"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"name":"pixelforge","prevSize":48,"code":59648},"setIdx":0,"setId":3,"iconIdx":0},{"icon":{"paths":["M976.272 538.191c0 11.223-7.016 22.448-14.5 30.867l-157.14 185.202c-27.126 31.802-82.311 57.055-123.469 57.055h-508.837c-16.837 0-40.688-5.146-40.688-26.191 0-11.223 7.016-22.448 14.5-30.867l157.14-185.202c27.126-31.802 82.311-57.055 123.469-57.055h508.837c16.837 0 40.688 5.146 40.688 26.191zM815.856 377.307v74.828h-389.112c-58.461 0-130.952 33.208-168.835 78.104l-159.949 188.009c0-3.74-0.467-7.951-0.467-11.691v-448.977c0-57.523 47.233-104.761 104.761-104.761h149.66c57.523 0 104.761 47.233 104.761 104.761v14.968h254.418c57.523 0 104.761 47.233 104.761 104.761z"],"attrs":[{}],"width":1074,"isMulticolor":false,"isMulticolor2":false,"tags":["folder-open"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":28,"name":"editor","code":59649},"setIdx":1,"setId":2,"iconIdx":0},{"icon":{"paths":["M511.573 85.333c235.947 0 427.094 191.147 427.094 426.667s-191.147 426.667-427.094 426.667c-235.52 0-426.24-191.147-426.24-426.667s190.72-426.667 426.24-426.667zM512 853.333c188.587 0 341.333-152.746 341.333-341.333s-152.746-341.333-341.333-341.333-341.333 152.746-341.333 341.333 152.746 341.333 341.333 341.333zM661.333 469.333c-35.413 0-64-28.586-64-64s28.587-64 64-64c35.414 0 64 28.587 64 64s-28.586 64-64 64zM362.667 469.333c-35.414 0-64-28.586-64-64s28.586-64 64-64c35.413 0 64 28.587 64 64s-28.587 64-64 64zM512 746.667c-99.413 0-183.893-62.294-218.027-149.334h436.054c-34.134 87.040-118.614 149.334-218.027 149.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE23D"],"defaultCode":57917,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":11,"order":26,"prevSize":32,"code":57917,"name":"pattern"},"setIdx":2,"setId":1,"iconIdx":0},{"icon":{"paths":["M511.573 791.040l314.88-244.907 69.547 54.187-384 298.667-384-298.667 69.12-53.76zM512 682.667l-384-298.667 384-298.667 384 298.667-69.973 54.187z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE34B"],"defaultCode":58187,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":14,"order":35,"ligatures":"","prevSize":32,"code":58187,"name":"segments"},"setIdx":2,"setId":1,"iconIdx":1},{"icon":{"paths":["M288.427 206.507l-60.587 60.16-76.373-76.374 60.16-60.16zM170.667 448v85.333h-128v-85.333h128zM554.667 23.467v125.866h-85.334v-125.866h85.334zM872.533 190.293l-76.373 76.374-60.16-60.16 76.373-76.374zM735.573 774.827l59.734-59.734 76.8 76.374-60.16 60.16zM853.333 448h128v85.333h-128v-85.333zM512 234.667c141.227 0 256 114.773 256 256 0 141.226-114.773 256-256 256s-256-114.774-256-256c0-141.227 114.773-256 256-256zM469.333 957.867v-125.867h85.334v125.867h-85.334zM151.467 791.040l76.373-76.8 60.16 60.16-76.373 76.8z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE333"],"defaultCode":58163,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":40,"order":73,"ligatures":"","prevSize":32,"code":58163,"name":"sun"},"setIdx":2,"setId":1,"iconIdx":2},{"icon":{"paths":["M512 128c212.053 0 384 152.747 384 341.333 0 117.76-95.573 213.334-213.333 213.334h-75.52c-35.414 0-64 28.586-64 64 0 16.213 6.4 31.146 16.213 42.24 10.24 11.52 16.64 26.453 16.64 43.093 0 35.413-28.587 64-64 64-212.053 0-384-171.947-384-384s171.947-384 384-384zM277.333 512c35.414 0 64-28.587 64-64s-28.586-64-64-64c-35.413 0-64 28.587-64 64s28.587 64 64 64zM405.333 341.333c35.414 0 64-28.586 64-64 0-35.413-28.586-64-64-64-35.413 0-64 28.587-64 64 0 35.414 28.587 64 64 64zM618.667 341.333c35.413 0 64-28.586 64-64 0-35.413-28.587-64-64-64-35.414 0-64 28.587-64 64 0 35.414 28.586 64 64 64zM746.667 512c35.413 0 64-28.587 64-64s-28.587-64-64-64c-35.414 0-64 28.587-64 64s28.586 64 64 64z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2B3"],"defaultCode":58035,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":75,"order":75,"prevSize":32,"code":58035,"name":"palette"},"setIdx":2,"setId":1,"iconIdx":3},{"icon":{"paths":["M512 192c213.333 0 395.52 132.693 469.333 320-73.813 187.307-256 320-469.333 320s-395.52-132.693-469.333-320c73.813-187.307 256-320 469.333-320zM512 725.333c117.76 0 213.333-95.573 213.333-213.333s-95.573-213.333-213.333-213.333-213.333 95.573-213.333 213.333 95.573 213.333 213.333 213.333zM512 384c70.827 0 128 57.173 128 128s-57.173 128-128 128-128-57.173-128-128 57.173-128 128-128z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE0E8"],"defaultCode":57576,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":172,"order":74,"prevSize":32,"code":57576,"name":"eye"},"setIdx":2,"setId":1,"iconIdx":4},{"icon":{"paths":["M640 42.667v85.333h-256v-85.333h256zM469.333 597.333v-256h85.334v256h-85.334zM811.947 315.307c52.48 65.706 84.053 148.906 84.053 239.36 0 212.053-171.52 384-384 384s-384-171.947-384-384c0-212.054 171.947-384 384-384 90.453 0 173.653 31.573 239.787 84.48l60.586-60.587c21.76 17.92 41.814 38.4 60.16 60.16zM512 853.333c165.12 0 298.667-133.546 298.667-298.666s-133.547-298.667-298.667-298.667-298.667 133.547-298.667 298.667 133.547 298.666 298.667 298.666z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE325"],"defaultCode":58149,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":370,"order":21,"ligatures":"","prevSize":32,"code":58149,"name":"speed"},"setIdx":2,"setId":1,"iconIdx":5},{"icon":{"paths":["M707.84 366.507l60.16 60.16-256 256-256-256 60.16-60.16 195.84 195.413z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE395"],"defaultCode":58261,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":549,"order":69,"ligatures":"","prevSize":32,"code":58261,"name":"expand"},"setIdx":2,"setId":1,"iconIdx":6},{"icon":{"paths":["M554.667 128v426.667h-85.334v-426.667h85.334zM760.747 220.587c82.773 70.4 135.253 174.506 135.253 291.413 0 212.053-171.947 384-384 384s-384-171.947-384-384c0-116.907 52.48-221.013 135.253-291.413l60.16 60.16c-66.986 54.613-110.080 137.813-110.080 231.253 0 165.12 133.547 298.667 298.667 298.667s298.667-133.547 298.667-298.667c0-93.44-43.094-176.64-110.507-230.827z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE08F"],"defaultCode":57487,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":557,"order":19,"ligatures":"","prevSize":32,"code":57487,"name":"power"},"setIdx":2,"setId":1,"iconIdx":7},{"icon":{"paths":["M816.64 551.936l85.504 67.584c8.192 6.144 10.24 16.896 5.12 26.112l-81.92 141.824c-5.12 9.216-15.872 12.8-25.088 9.216l-101.888-40.96c-20.992 15.872-44.032 29.696-69.12 39.936l-15.36 108.544c-1.024 10.24-9.728 17.408-19.968 17.408h-163.84c-10.24 0-18.432-7.168-20.48-17.408l-15.36-108.544c-25.088-10.24-47.616-23.552-69.12-39.936l-101.888 40.96c-9.216 3.072-19.968 0-25.088-9.216l-81.92-141.824c-4.608-8.704-2.56-19.968 5.12-26.112l86.528-67.584c-2.048-12.8-3.072-26.624-3.072-39.936s1.536-27.136 3.584-39.936l-86.528-67.584c-8.192-6.144-10.24-16.896-5.12-26.112l81.92-141.824c5.12-9.216 15.872-12.8 25.088-9.216l101.888 40.96c20.992-15.872 44.032-29.696 69.12-39.936l15.36-108.544c1.536-10.24 9.728-17.408 19.968-17.408h163.84c10.24 0 18.944 7.168 20.48 17.408l15.36 108.544c25.088 10.24 47.616 23.552 69.12 39.936l101.888-40.96c9.216-3.072 19.968 0 25.088 9.216l81.92 141.824c4.608 8.704 2.56 19.968-5.12 26.112l-86.528 67.584c2.048 12.8 3.072 26.112 3.072 39.936s-1.024 27.136-2.56 39.936zM512 665.6c84.48 0 153.6-69.12 153.6-153.6s-69.12-153.6-153.6-153.6-153.6 69.12-153.6 153.6 69.12 153.6 153.6 153.6z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE0A2"],"defaultCode":57506,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":562,"order":29,"ligatures":"","prevSize":32,"code":57506,"name":"settings"},"setIdx":2,"setId":1,"iconIdx":8},{"icon":{"paths":["M556.8 417.707l125.867 94.293-256 192v-384zM556.8 417.707l125.867 94.293-256 192v-384zM556.8 417.707l-130.133-97.707v384l256-192zM469.333 173.653c-62.293 7.68-119.040 32.427-166.4 69.12l-60.586-61.013c63.146-51.627 141.226-85.76 226.986-94.293v86.186zM242.773 302.933c-36.693 47.36-61.44 104.107-69.12 166.4h-86.186c8.533-85.76 42.666-163.84 94.293-226.986zM173.653 554.667c7.68 62.293 32.427 119.040 69.12 165.973l-61.013 61.013c-51.627-63.146-85.76-141.226-94.293-226.986h86.186zM242.347 842.24l60.586-61.013c47.36 36.693 104.107 61.44 166.4 69.12v86.186c-85.333-8.533-163.84-42.666-226.986-94.293zM938.667 512c0 220.16-167.254 401.92-381.867 424.533v-86.186c167.253-22.187 296.533-165.547 296.533-338.347s-129.28-316.16-296.533-338.347v-86.186c214.613 22.613 381.867 204.373 381.867 424.533z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE139"],"defaultCode":57657,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":595,"order":46,"ligatures":"","prevSize":32,"code":57657,"name":"playlist"},"setIdx":2,"setId":1,"iconIdx":9},{"icon":{"paths":["M386.4 93.333c231.104 0 418.667 187.563 418.667 418.667s-187.563 418.667-418.667 418.667c-43.96 0-85.827-6.699-125.6-19.259 169.979-53.171 293.067-211.845 293.067-399.408s-123.088-346.237-293.067-399.408c39.773-12.56 81.64-19.259 125.6-19.259z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2A2"],"defaultCode":58018,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":607,"order":34,"ligatures":"","prevSize":32,"code":58018,"name":"night"},"setIdx":2,"setId":1,"iconIdx":10},{"icon":{"paths":["M512 85.333c235.947 0 426.667 190.72 426.667 426.667s-190.72 426.667-426.667 426.667-426.667-190.72-426.667-426.667 190.72-426.667 426.667-426.667zM725.333 665.173l-153.173-153.173 153.173-153.173-60.16-60.16-153.173 153.173-153.173-153.173-60.16 60.16 153.173 153.173-153.173 153.173 60.16 60.16 153.173-153.173 153.173 153.173z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE38F"],"defaultCode":58255,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":662,"order":50,"ligatures":"","prevSize":32,"code":58255,"name":"cancel"},"setIdx":2,"setId":1,"iconIdx":11},{"icon":{"paths":["M512 170.667c188.587 0 341.333 152.746 341.333 341.333 0 66.987-19.626 129.28-52.906 181.76l-62.294-62.293c19.2-35.414 29.867-76.374 29.867-119.467 0-141.227-114.773-256-256-256v128l-170.667-170.667 170.667-170.666v128zM512 768v-128l170.667 170.667-170.667 170.666v-128c-188.587 0-341.333-152.746-341.333-341.333 0-66.987 19.626-129.28 52.906-181.76l62.294 62.293c-19.2 35.414-29.867 76.374-29.867 119.467 0 141.227 114.773 256 256 256z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE116"],"defaultCode":57622,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":709,"order":17,"ligatures":"","prevSize":32,"code":57622,"name":"sync"},"setIdx":2,"setId":1,"iconIdx":12},{"icon":{"paths":["M384 689.92l451.84-451.413 60.16 60.16-512 512-238.507-238.507 60.587-60.16z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE390"],"defaultCode":58256,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":733,"order":56,"ligatures":"","prevSize":32,"code":58256,"name":"confirm"},"setIdx":2,"setId":1,"iconIdx":13},{"icon":{"paths":["M853.333 370.773l141.227 141.227-141.227 141.227v200.106h-200.106l-141.227 141.227-141.227-141.227h-200.106v-200.106l-141.227-141.227 141.227-141.227v-200.106h200.106l141.227-141.227 141.227 141.227h200.106v200.106zM512 768c141.227 0 256-114.773 256-256s-114.773-256-256-256-256 114.773-256 256 114.773 256 256 256zM512 341.333c94.293 0 170.667 76.374 170.667 170.667s-76.374 170.667-170.667 170.667-170.667-76.374-170.667-170.667 76.374-170.667 170.667-170.667z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2A6"],"defaultCode":58022,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":785,"order":15,"ligatures":"","prevSize":32,"code":58022,"name":"brightness"},"setIdx":2,"setId":1,"iconIdx":14},{"icon":{"paths":["M85.333 725.333v-42.666h128v170.666h-128v-42.666h85.334v-21.334h-42.667v-42.666h42.667v-21.334h-85.334zM128 341.333v-128h-42.667v-42.666h85.334v170.666h-42.667zM85.333 469.333v-42.666h128v38.4l-76.8 89.6h76.8v42.666h-128v-38.4l76.8-89.6h-76.8zM298.667 213.333h597.333v85.334h-597.333v-85.334zM298.667 810.667v-85.334h597.333v85.334h-597.333zM298.667 554.667v-85.334h597.333v85.334h-597.333z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE22D"],"defaultCode":57901,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":797,"order":58,"ligatures":"","prevSize":32,"code":57901,"name":"nodes"},"setIdx":2,"setId":1,"iconIdx":15},{"icon":{"paths":["M810.667 554.667h-256v256h-85.334v-256h-256v-85.334h256v-256h85.334v256h256v85.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE18A"],"defaultCode":57738,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":803,"order":59,"ligatures":"","prevSize":32,"code":57738,"name":"add"},"setIdx":2,"setId":1,"iconIdx":16},{"icon":{"paths":["M128 736l471.893-471.893 160 160-471.893 471.893h-160v-160zM883.627 300.373l-78.080 78.080-160-160 78.080-78.080c16.64-16.64 43.52-16.64 60.16 0l99.84 99.84c16.64 16.64 16.64 43.52 0 60.16z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE2C6"],"defaultCode":58054,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":834,"order":72,"ligatures":"","prevSize":32,"code":58054,"name":"edit"},"setIdx":2,"setId":1,"iconIdx":17},{"icon":{"paths":["M576 28.587c166.827 133.546 277.333 338.773 277.333 568.746 0 188.587-152.746 341.334-341.333 341.334s-341.333-152.747-341.333-341.334c0-144.213 51.626-276.906 137.813-379.306l-1.28 15.36c0 87.893 66.56 159.146 154.88 159.146 87.893 0 145.493-71.253 145.493-159.146 0-91.734-31.573-204.8-31.573-204.8zM499.627 810.667c113.066 0 204.8-91.734 204.8-204.8 0-59.307-8.534-117.334-25.174-172.374-43.52 58.454-121.6 94.72-197.12 110.080-75.093 15.36-119.893 64-119.893 133.12 0 74.24 61.44 133.974 137.387 133.974z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE409"],"defaultCode":58377,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":871,"order":10,"ligatures":"","prevSize":32,"code":58377,"name":"intensity"},"setIdx":2,"setId":1,"iconIdx":18},{"icon":{"paths":["M938.667 394.24l-232.534 201.813 69.547 299.947-263.68-159.147-263.68 159.147 69.973-299.947-232.96-201.813 306.774-26.027 119.893-282.88 119.893 282.454zM512 657.067l160.853 97.28-42.666-182.614 141.653-122.88-186.88-16.213-72.96-172.373-72.533 171.946-186.88 16.214 141.653 122.88-42.667 182.613z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE410"],"defaultCode":58384,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":927,"order":9,"ligatures":"","prevSize":32,"code":58384,"name":"star"},"setIdx":2,"setId":1,"iconIdx":19},{"icon":{"paths":["M512 85.333c235.52 0 426.667 191.147 426.667 426.667s-191.147 426.667-426.667 426.667-426.667-191.147-426.667-426.667 191.147-426.667 426.667-426.667zM554.667 725.333v-256h-85.334v256h85.334zM554.667 384v-85.333h-85.334v85.333h85.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE066"],"defaultCode":57446,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":952,"order":62,"ligatures":"","prevSize":32,"code":57446,"name":"info"},"setIdx":2,"setId":1,"iconIdx":20},{"icon":{"paths":["M256 810.667v-512h512v512c0 46.933-38.4 85.333-85.333 85.333h-341.334c-46.933 0-85.333-38.4-85.333-85.333zM810.667 170.667v85.333h-597.334v-85.333h149.334l42.666-42.667h213.334l42.666 42.667h149.334z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE037"],"defaultCode":57399,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":969,"order":55,"ligatures":"","prevSize":32,"code":57399,"name":"del"},"setIdx":2,"setId":1,"iconIdx":21},{"icon":{"paths":["M704 128c131.413 0 234.667 103.253 234.667 234.667 0 161.28-145.067 292.693-364.8 491.946l-61.867 56.32-61.867-55.893c-219.733-199.68-364.8-331.093-364.8-492.373 0-131.414 103.254-234.667 234.667-234.667 74.24 0 145.493 34.56 192 89.173 46.507-54.613 117.76-89.173 192-89.173zM516.267 791.467c203.093-183.894 337.066-305.494 337.066-428.8 0-85.334-64-149.334-149.333-149.334-65.707 0-129.707 42.24-151.893 100.694h-79.787c-22.613-58.454-86.613-100.694-152.32-100.694-85.333 0-149.333 64-149.333 149.334 0 123.306 133.973 244.906 337.066 428.8l4.267 4.266z"],"isMulticolor":false,"isMulticolor2":false,"tags":["uniE04C"],"defaultCode":57420,"grid":0,"attrs":[]},"attrs":[],"properties":{"id":1034,"order":61,"ligatures":"","prevSize":32,"code":57420,"name":"presets"},"setIdx":2,"setId":1,"iconIdx":22}],"height":1024,"metadata":{"name":"wled122"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"i-","metadata":{"fontFamily":"wled122","majorVersion":1,"minorVersion":7},"metrics":{"emSize":1024,"baseline":20,"whitespace":0},"embed":false,"autoHost":true,"noie8":true,"ie7":false,"showSelector":false,"showMetrics":false,"showMetadata":false,"showVersion":true},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"name":"icomoon","classSelector":".icon"},"historySize":50,"quickUsageToken":{"wled122":"MDA1MGUxOTY0MyMxNzczNTY5NTMxI0ljcTJCSm9WQnFUUUFOdUZHQzlpaFZ0QkZaOGRFTXFRWFJoU1VjN2VqbmFn"},"showLiga":false,"gridSize":16}} \ No newline at end of file diff --git a/wled00/data/icons-ui/style.css b/wled00/data/icons-ui/style.css index 59982557d0..829559fffa 100644 --- a/wled00/data/icons-ui/style.css +++ b/wled00/data/icons-ui/style.css @@ -1,10 +1,9 @@ @font-face { font-family: 'wled122'; src: - url('fonts/wled122.woff2?e3eban') format('woff2'), - url('fonts/wled122.ttf?e3eban') format('truetype'), - url('fonts/wled122.woff?e3eban') format('woff'), - url('fonts/wled122.svg?e3eban#wled122') format('svg'); + url('fonts/wled122.ttf?yzxblb') format('truetype'), + url('fonts/wled122.woff?yzxblb') format('woff'), + url('fonts/wled122.svg?yzxblb#wled122') format('svg'); font-weight: normal; font-style: normal; font-display: block; @@ -25,6 +24,12 @@ -moz-osx-font-smoothing: grayscale; } +.i-pixelforge:before { + content: "\e900"; +} +.i-editor:before { + content: "\e901"; +} .i-pattern:before { content: "\e23d"; } diff --git a/wled00/data/index.css b/wled00/data/index.css index 6b8e068a00..f3a9f8466a 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -1,6 +1,6 @@ @font-face { font-family: "WIcons"; - src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAnUAAsAAAAAE1AAAAmFAAGZmgAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgXwRCAqcYJZIATYCJANwCzoABCAFgwYHIBs7D8iOwzgm3MXMnzZCktnjcbN+QlJLaJ3ulULplpW6UqWioeS91Jye0jUlJwZr5nTdE3LntdPvAg+ft/fbsLsGlNLuhlmQjKi7NPDEIgwTmP//a6mdl+SHUBhEIdHFxak7s4E/yzhJSjC7BQQLfDwopF/i6aqSElEFDXx8ZVWjy3rym4N6FlZQ4hu+nXsGIDMQF3gAxa14AgArtVMhfkgjfEAbiChwuSIwEUCmudPhiQdT6rvIjLSRZEwDhF9BIsooI53TIRIoIUD8kyNZI7UjAyMrR/aM/DwaOpozah9LGCsY2zN2YOzs2L3xqeNp4zXjq8bXT/hMBLj/53YDAIS+7u668n3H+HRPdZd1u3TzdRZdVMTfIl5HfKgd1b7Svqd9W9uprdP8QTOmeaz5TPORJlDDjHVjG0ANMQYsmRrKlmpyqV7kubIQC2GSIkFS+MneCJ48JJFVChQfuwKMp2yU9pmq1VKUR6ret0Gp0SjVYRRF+Xj7+OiUSk/GIzu1miHZWx+g8Y1RUktPmqIitRTXVNzzCtuFPKcH0zRBG+Y9/CnhBa20v5oHfsEUMgXMPEfO5ZcJx0FIPiVywgjb6MIuV+oZ4v2kk6/znIxDKrguM22y+bW8wUGqi7aL8fQJzwnCj8tIppdI9bYDSVJVCQInipW0HbtclcT7vCyLmXaSVrQSNMybaJJBh2PiXrXbgd6AbqecdDTO9EQEIeW0VPWQcdQ8ltPOEu+76q2IxUToJeWpfjQiHHH5AsADLj1bHgQxXsUoHfKYbg+CxCxC69eHcOvWheJ1l6b0nD7jG+bSA1dCZVxmw8ZJ/IYtxPtbJxlpQ/LGjSq00TmdNIZxrGel+y+rZJro+nUh3PrNIGwK6WrXNMV2xTeRWHSjScktLJfe1rc7spyvk3b6V4k48Sr3Am1Pv/QifhsI2uMvc863OiQQRNoedpPfHnSwcete+aDEE67cKzTgBlQgjpjgTDnJtGnX2qbmXJ6FOBLZ7wsr+JZzYnbjdbkCuEfU0HvlwqbtUgJ7zRXFNJsvSxlwz2WYta4xjri/fsulnnFVPyonpP0RL5oVNKkkfElG4csTDNAsgzC38G7gSKVgSZ7m/cEvKALmxKz//u7h6egHF7MrH4jJp/Zx4q32a8T71xnHVRCGlfFZNttd2FcUaay6e9PkhucyR0oPu1z1z/DB+8wixAFdMU1gnmB4xAw68pwHcWjlFrBnXxLjj63UGgvNGVGAJFzxFw+Womn7MAibVbu6leHRB5sc10fLtbrdr/JqV6Yr+ovwFtRHE7M4zG90qNB6YREoo51kFJabq3NeHVKdef/hsMFFSpt5m8XmJqDDAnR0c418mxmxrQzQuyPnspRwfAYkpthzr7gST1xNSf4WtBMM9DQT19uL+gb47gFLP3cT08F8I4dZxJl41Gsx9WHzLBOHzWjRS9NLCOUBCFQ+uGhB/V7ZzUwKESTmDriJ+UecdD/bFXFMLLsjgiAt4pp7ulpxb2tzE8I8xhyHODBK3SGg6QP12BiP3YMw2rDFtWUDXL+esnv3H9QxqfmbDnbMLjGUFpqqZbnWSg0lhWv9wU35qTHqP9zqUrL7kqKj8YjZzg01pb9+yQ8sXZpYxKGiFJTNsIwwpyR44gEOnV/+ennFdHD/2lQ3uS5y1qzIztXUNPE6odYJ0PqUiWJtgKGKMILY60dxeYynbb+sFKKqNn0Wz2rLtMbBQWPnYtmJa4WqFRob/9mmuycQVv7ifCNvXrlhzgDLDvAGA+8H5xjK948cDet+FaXfS+Lko/Wt+vScqarq6kZTbk4NaKqpObkEEpsac9L1rRNXJgPbrWyDdYje6tBQAztkbYC0wDe4UnNipmnZtInu/ujf6Kf7ve112Huf92Ev/7enB/+nP7pbrPiQJZbi0jCSpoN9UNPTkj7JMwpbWgopAbhtbOWkytAF3K+/qo0SASNW2G2bLfnshpB4a9dmz7/Hx//dc3OXNZ46YRyXUV2dYRsD97qKL79qazu+vSI1vPXT7375bWSGocBofD2eIRzJ0cMC0tenwQ0gfvuSdvd14f1uEooLPE3JJHL6uCd/n5n8d35UOKPn6nhr8kyrV3ad3nz2iTiNL414EnefL/JGLlWZtZWaqoEh4xSjvsGb/6m9raFlsLm4uHkQWlv7T/weZzjHHe7xZiUzpJ5WAWBLDNwRKxwRYnFoXGxcaKxN6DR8BNn2o9Nqmmutvra5TnIjXMBlmIFZ3yPYX3Mt9v5mmHuwYvvxPverL9eSvszXNjUXrkbqcGOVW2bEbDGKi3MLVTWzzWHF54Bu/2rA1qko6l9fFgVbBurfVBWFFlVW1ugxOwcs+8W//FcUZJieLl9WXA8eGL5crB7fhOMyxl8bjQWGjB1bW/ok6Ucqensr7F8H7utsmdqoHmz99rvyeE/Pz7u64mvVXLjyY8v8j5XhZeH3aPX75dpiO5eN/OzwcG7zkflt/sd5e7YcqbOowfRg22R5585at2vXX87W1Y0gQ079497eYT1EkyoEqMYABmHd8QvKGrRG6bJYTDCCZYGEWcm5G1jXM2i54Y9WtiBuklP57YtBZMAWlu2fYzDM7Q+5FmxKS3Oz5jwK6IactbWPowuQgNyHluKlaw9wnbOmtuajo/VSw9FrBSRwMcuUV2ZwFhh6s7hsqriWCsgA2s3nFcri4I7O+asxwxZbtLL03E9bhcR6Yz9mIbF0U96K0xGA7bx9y+l2//73j+H2i0EGd27uAVNI/WhCYuWqIDaYxads0lcVFV+dOlHmBx/qO7c6/uZX0tReUtJQv64y3adAvX6xDezAX/8Wm8Cgh/95O9OxsNCYnsXWQ+7pCz8/NMZ57ZAIGEdTw+ap8V+I3NUVe375wiv+lccqj172X7Yw5gJAUQGYPQ6QyxRfgeC+Qc5WnAMCAHFv6TJtet3pn/83b4YCAIBv35ofpTRyt5PjZEwT8KYAEQK8nFgBcE/yUwn2oqHSBKoEG7KZQLMpjo5uha/PI2yuBWOCTSDZajpqQ68+Za18jgGgYMT8nBhjKcFrKCYF6yKSZRLF5tR5YKhUzzNWM52mBvuPMiL7xPx4UaRgFiJZAVFscZ2HUIhUPcEaH5WWDvvmvdPfl5KaCvO8o1+fFCBb6hvuLz8lMROwfjPN8iar90RCCiRCJr3ugqHf6LqgUYYs5hzvu9tMIOUr/xpvRsNVvdZ/p+mB8n7V2Spo0T+aRhPpNhsNFOqxoE2u0suqTipgx58IJA0AAAA=) format('woff'); + src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAsAAA0AAAAAFlgAAAqqAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACEGhEICp08lnkLPgABNgIkA3gEIAWDGweCNhvxEVGUcFI3wBeFsYOmlCFXadeSCl4PGhMTwyMh0q9d2MXuDaeszCMkmT3Abd0Eu2ijAIMUa1IDbaQmRj/wndtnJB+d8BHN/+ZKv+zJJpUCCAsMA5IcArBbtlteAg6ToYi3nPp6KxH97fd9OQgssMYTSymghAPMMODmLNpvv/P8BPzeodosVKppyCRNZE0QEqlTCp0SqP9T4O4gAMzzFuTJg2RPa6/23s/f4IYKREKfr6tTc/cLu7dh2JTwmhJdUiSLQqZVQFvmy6mScazQAwlZ7apjDAOl7l8dYEyN5azo7xRYCTCz7gCAzIa7hoI38uBn9/NfQMIrA5RCyCOfOtya0oEneAKP2+M8AEzujgX5QIQYkXEhC5nk4BVC6f6L4cmN4YazURxLPmVQjD4XkFWhNcfmv38+EMNisJkOyOKfgx6n/2z9efLjZY9fPol6EvJEdaY7I5y1zu3Ok64kl58r7bcprplPfZ+GvELuPwEAiGmvZJPj8ErdT9kXF+1jV7AvsG3seNY31uuFw5m/LLgKwNzGLGd8mO+cfw6A8S5jCsM/9wfEH8iWrJEYBLUxMHfsLJpcHQqzOuDEFhQjM1otoVvVg94O/zMIoCJtI1ACwThSfr8yQL1KvQ5rAApCJOJJKBSl4cdB3IwhcY7A5i3/hNDuIJq7NmfVLJNq2Z1hACMTkEpSDwPzGMtL6Qj7EFl8BemVw4zAppSRHW5ZhSxVZIZwKIDXduoxP57T0cgYeukZbC1afoHHq6/OwUSERJEC0lcLGXjp0QKyd7tOLYzdaXLTFHYixavtddgQ0YyI6xbZbLleW+DKSDGxqrvjTWIRtNNgOF6yGYc0ZhihH0R1vR9WuWn2Q4pkWdcmW0QsbEIYzglYJKxhzbvPBSWhn9uiMsuraZ3jiQ75dBgpD4mW9tgSdSHFzLzEcnLiNDvb59n9lVxzrObWObWDviOG3Dwt5RZCKdLLyl04L0+xvKG6aEG0nJFTM6AcuXROdpzmFJCcH9+uWfmohYxDH0Nxk+nRN4ZT3uJW3O32b9GChl57lSFlYeur0F6s+ve/cC8GeUHLy5CeTZoB7XGeFaxDWspDQ9CBaXdnUZU9hGerGTqIgUtgQxhFauojOOdYXo78csyahwycYlRk/FVxQdrYrQc7r1tJQJv1+Xi5FbW+xPCwj5pLicU1YATAPRM9hVc9RfdxWc2300x9lIgM3K/9xgtYHI8miESYICECeMSQt3EtAdq7jhUlLE2CiYgNqUeZNrzc9nLTTg+EeckP9Kz28vnwTeoolOtCGyF5WOonuVZNPkHX/RKff2/l48rnCUbIfJZad59cYhSwkWPEJUQkRvZrYUMVbAKCS6jB/bp7M2ItABfEMpgBinhBFLgze5jkAlW62xjORdV67XqlRsPsObLU7cI+/4ss97HdGJ2iXMrTFMuRTzAe2SISYd9NlE6rZmS4ahqS+8GKTA7ZuWs9YGQfYGQHdUqbXcy+iQC2aiEDhkdLTkhvpoYOmp6tTc6yvgVbEIGdkoPu2sV275V27N23h7awKFxyUm5n1CGxXfscu7nrlINyF7v00vEyotuwG5If4LpYtazK+s3xmi4bpC2UoPNVnRa9JubCZj3+jg4Zl+iGnds38V2bNqxnXOKcUkYv8Vw1vppL4+lMDDMok9jqbFmxHE1LeYp/Sc6O03odj1droeRpqckiE7Qa4jB+nO5OlVyIymtCtJdACJKcTKe3Kct4DL+2knGWW/gpzKXr191XULH0Ay1NmD9ndUMvJaoqrCq5dStqFaosxPyr8/N902gfWD5BcFtmaqreo8wxq1T1+g6+d8iQDLnRJBeYZP4jf/MEBpHR0Lj1zmvSecXw/+vqjLhyTs+enLSoujoiRy3LDbIhvmtxCTAzTZPZBNzr683+Pi7U/TOZjE+Z8yHfzlQzMbsdS4t1ulIwTTJN6/hj5nBM5GevHDFhfTVob+tnthVHUVyu6o1q8GeQCn5TYowqQ4a0asLK33fsSX2zLCVo473WZ4XPWu/gTUr4n1nSfH38mHmqzKpYCucxNo9yXJO1toU1NYX8GuAm7EXRRVH9ja9f0zCPBxUQoNvXeb64MoLftWmu23d39+9eBU1d+UObPKOkpETCw4F7hvbO3brNG0u0Qnrt6B9fveVI0AIMu+aSkOtc3VrSJG5IwMsAv2Rwvfs6ObS5xyXIGfFGlW5cxjv/b4+s7/gTclsCLce7ZvXo6i3rJxi2P9ln4irW+XW89OtSmD3FBmYRo9jaDUvEEip98Bf1mytr7BaFwmJXXVf/AsfRQx8c8MBdywDCjkgAM7s2GDeXXEdyeRSPy4viEmSqzesYgTclp1nKvv50S/kNN+Me01EF0wbWprFZyoBXWACDKu3Cljz17p1WbIZ7xFwjnWai0bGQqsZQK2xf3jggsrSXIVaxQ5EaS2GoE0/jlHG6deccNaU4PqGWZWrG4+588wUzl9saGzWaiLzKjH1B/XEQ4LgwcYIvPn16iYkW1K9gpBLXayyhAJWUWWu2o4jXaVtbtfzXgfuQTk3DaPbaBw817l7OvamJX0Yz0gPOtn4jx9N79MYQbCTF84i+sxz6kXTj3MYcbvkx56XzGsMoWng/EOvWrcWLo2/Jki/by8srSCjHsse7du1fBqtFNQfTTAMOYnfw+6srmZgvttlWFUunU+SoXWtJpU4qtduaqVndnxftCHhw2c5Qs43pa9cbRfu0y1Nt5oTN6hPvfS+w6LgjvexcaoGJZO76IeYh02unz5FWVjqiKer9q78ieyU0Da60eLSoAM296/BJHbMKCIXs4Xs17vLgTs35ikkIrh9BLc4dTXAxNvU5UvV1Vb7bhkO4BhD/9lGHO+/fn4NjlwtHhQO8BSSK5a9HRtGUqfZwnbmeTb2rTbpb764lHTY8Ydt87VtQbHW9UlkZn5WaPRqobxB3qLN+/cb18J+f+dNROn5AISbO1lVAbseul2ewdd4vjwdVkzC2L02fKWdJE3fnxAH7JhVtSF4/EDxhQNoukP0c++bTOk5j6JfTPn6EbndfYOD6FcsJIgKUob1Inz6u5zRZLPsWD0IB4t4DWzCg1XLY/wIGg30NHTTauPsJKVtSOtJ9O2/rYgfF03zzHqybNYqD/yx4tforP6Ld9vAr2ybl/3yIRTcdrwzuetFFSSMAH0LMxI2+fkDdCcDYJyA3ipitETmBOLi8EZmJSOpOPFq/DTxMGhrE3JLs83kymayp5Uh8Ms2xDiHtOJqBLNjEdz8eyLwgrYDkpX6syTp5sNVEYdFEZesHeyLOS68ey57lZy682pmLOqOJ4wcS2GwSmTfZWPLgMWbCdumm7N32YP5QDH110k4bAfiCL0Df065NIHyl/q626c2Y16wHeIviHYE4G+iT5oGtK/bUXlddcGyeJwQBPKxxgIKM7PhKE0/2uuQ+juqSmmzG3PDQFXfqjwMpWpmyPLpjTQbA8zda3OddU9za9W/xDBTYht7SfiikklBMEosFGw5ceGBX1J+TRABBhBBGBFHEEIeCD40EkkghjYx77NI+y02QY4JeWJYom4tVXCrlMg1XCDMwWSeBQMpFORkyRSehUM1EmQphXMqVogyVtJNIKOEiERcruUyTmZOVJOkkzsrJEGRl5WR5AgA=); } :root { @@ -94,6 +94,11 @@ button { cursor: pointer; } +.iconlabel{ + font-size:12px; + margin-top:2px; +} + .labels { margin: 0; padding: 8px 0 2px 0; diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e37844f0c2..09207f06d6 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -10,7 +10,7 @@ WLED - +
Loading WLED UI...
@@ -126,12 +126,28 @@
-
- - - - + +
+
+ +
Files
+
+
+ +
PixelForge
+
+
+ +
Palettes
+
+

Color palette

@@ -364,8 +380,9 @@ - + diff --git a/wled00/data/index.js b/wled00/data/index.js index 7cb989d062..c6e620628c 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -8,6 +8,7 @@ var segLmax = 0; // size (in pixels) of largest selected segment var selectedFx = 0; var selectedPal = 0; var csel = 0; // selected color slot (0-2) +var cpick; // iro color picker var currentPreset = -1; var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; @@ -42,16 +43,24 @@ var hol = [ [0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year ]; -var cpick = new iro.ColorPicker("#picker", { - width: 260, - wheelLightness: false, - wheelAngle: 270, - wheelDirection: "clockwise", - layout: [{ - component: iro.ui.Wheel, - options: {} - }] -}); +// load iro.js sequentially to avoid 503 errors, retries until successful +(function loadIro() { + const l = d.createElement('script'); + l.src = 'iro.js'; + l.onload = () => { + cpick = new iro.ColorPicker("#picker", { + width: 260, + wheelLightness: false, + wheelAngle: 270, + wheelDirection: "clockwise", + layout: [{component: iro.ui.Wheel, options: {}}] + }); + d.readyState === 'complete' ? onLoad() : window.addEventListener('load', onLoad); + }; + l.onerror = () => setTimeout(loadIro, 100); + document.head.appendChild(l); +})(); + function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} function sCol(na, col) {d.documentElement.style.setProperty(na, col);} @@ -688,7 +697,7 @@ function populateInfo(i) var vcn = "Kuuhaku"; if (i.cn) vcn = i.cn; - cn += `v${i.ver} "${vcn}"

+ cn += `v${i.ver} "${vcn}"${i.release ? '
('+i.release+')' : ''}

${urows} ${urows===""?'':''} ${i.opt&0x100?inforow("Debug",""):''} @@ -698,13 +707,17 @@ ${inforow("Uptime",getRuntimeStr(i.uptime))} ${inforow("Time",i.time)} ${inforow("Free heap",(i.freeheap/1024).toFixed(1)," kB")} ${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""} + +${i.leds.count?inforow("Total LEDs",i.leds.count):""} ${inforow("Estimated current",pwru)} ${inforow("Average FPS",i.leds.fps)} + ${inforow("MAC address",i.mac)} ${inforow("CPU clock",i.clock," MHz")} ${inforow("Flash size",i.flash," MB")} ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} -${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} +${inforow("Environment",i.arch + " " + i.core + ( i.lwip ? " (" + i.lwip + ")" : ""))} +${i.repo?inforow("GitHub","" + i.repo + ""):""}



`; gId('kv').innerHTML = cn; // update all sliders in Info @@ -972,8 +985,6 @@ function populatePalettes() ); } } - if (li.cpalcount>0) gId("rmPal").classList.remove("hide"); - else gId("rmPal").classList.add("hide"); } function redrawPalPrev() @@ -1056,6 +1067,11 @@ function btype(b) case 34: return "ESP32-S3"; case 5: case 35: return "ESP32-C3"; + case 39: return "ESP32-C6"; + case 40: return "ESP32-C61"; + case 41: return "ESP32-C5"; + case 42: + case 43: return "ESP32-P4"; case 1: case 82: return "ESP8266"; } @@ -1645,14 +1661,10 @@ function setEffectParameters(idx) paOnOff[0] = paOnOff[0].substring(0,dPos); } if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; - gId("adPal").classList.remove("hide"); - if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide"); } else { // disable palette list text += ' not used'; palw.style.display = "none"; - gId("adPal").classList.add("hide"); - gId("rmPal").classList.add("hide"); // Close palette dialog if not available if (palw.lastElementChild.tagName == "DIALOG") { palw.lastElementChild.close(); @@ -3290,8 +3302,14 @@ function checkVersionUpgrade(info) { const storedVersion = versionInfo.version || ''; if (storedVersion && storedVersion !== currentVersion) { - // Version has changed, show upgrade prompt - showVersionUpgradePrompt(info, storedVersion, currentVersion); + // Version has changed + if (versionInfo.alwaysReport) { + // Automatically report if user opted in for always reporting + reportUpgradeEvent(info, storedVersion, true); + } else { + // Show upgrade prompt + showVersionUpgradePrompt(info, storedVersion, currentVersion); + } } else if (!storedVersion) { // Empty version in file, show install prompt showVersionUpgradePrompt(info, null, currentVersion); @@ -3301,7 +3319,7 @@ function checkVersionUpgrade(info) { console.log('Failed to load version-info.json', e); // On error, save current version for next time if (info && info.ver) { - updateVersionInfo(info.ver, false); + updateVersionInfo(info.ver, false, false); } }); } @@ -3327,7 +3345,7 @@ function showVersionUpgradePrompt(info, oldVersion, newVersion) { ? `You are now running WLED ${newVersion}.` : `Your WLED has been upgraded from ${oldVersion} to ${newVersion}.`; - const question = 'Help make WLED better with a one-time hardware report? It includes only device details like chip type, LED count, etc. — never personal data or your activities.' + const question = 'Help make WLED better by sharing hardware details like chip type and LED count? This helps us understand how WLED is used and prioritize features — we never collect personal data or your activities.' dialog.innerHTML = `

${title}

@@ -3336,10 +3354,15 @@ function showVersionUpgradePrompt(info, oldVersion, newVersion) {

Learn more about what data is collected and why

-
- - - +
+ +
+
+ +
`; @@ -3348,23 +3371,27 @@ function showVersionUpgradePrompt(info, oldVersion, newVersion) { // Add event listeners gId('versionReportYes').addEventListener('click', () => { - reportUpgradeEvent(info, oldVersion); + const saveChoice = gId('versionSaveChoice').checked; d.body.removeChild(overlay); + // Pass saveChoice as alwaysReport parameter + reportUpgradeEvent(info, oldVersion, saveChoice); }); gId('versionReportNo').addEventListener('click', () => { - // Don't update version, will ask again on next load - d.body.removeChild(overlay); - }); - - gId('versionReportNever').addEventListener('click', () => { - updateVersionInfo(newVersion, true); + const saveChoice = gId('versionSaveChoice').checked; d.body.removeChild(overlay); - showToast('You will not be asked again.'); + if (saveChoice) { + // Save "never ask" preference + updateVersionInfo(newVersion, true, false); + showToast('You will not be asked again.'); + } else { + // Save current version to prevent re-prompting until version changes + updateVersionInfo(newVersion, false, false); + } }); } -function reportUpgradeEvent(info, oldVersion) { +function reportUpgradeEvent(info, oldVersion, alwaysReport) { showToast('Reporting upgrade...'); // Fetch fresh data from /json/info endpoint as requested @@ -3391,8 +3418,8 @@ function reportUpgradeEvent(info, oldVersion) { }; // Add optional fields if available - if (infoData.psramPresent !== undefined) upgradeData.psramPresent = infoData.psramPresent; // Whether device has PSRAM - if (infoData.psramSize !== undefined) upgradeData.psramSize = infoData.psramSize; // Total PSRAM size in MB + if (infoData.psrSz !== undefined) upgradeData.psramSize = infoData.psrSz; // Total PSRAM size in MB; can be 0 + // Note: partitionSizes not currently available in /json/info endpoint // Make AJAX call to postUpgradeEvent API @@ -3406,8 +3433,12 @@ function reportUpgradeEvent(info, oldVersion) { }) .then(res => { if (res.ok) { - showToast('Thank you for reporting!'); - updateVersionInfo(info.ver, false); + if (alwaysReport) { + showToast('Thank you! Future upgrades will be reported automatically.'); + } else { + showToast('Thank you for reporting!'); + } + updateVersionInfo(info.ver, false, !!alwaysReport); } else { showToast('Report failed. Please try again later.', true); // Do NOT update version info on failure - user will be prompted again @@ -3420,10 +3451,11 @@ function reportUpgradeEvent(info, oldVersion) { }); } -function updateVersionInfo(version, neverAsk) { +function updateVersionInfo(version, neverAsk, alwaysReport) { const versionInfo = { version: version, - neverAsk: neverAsk + neverAsk: neverAsk, + alwaysReport: !!alwaysReport }; // Create a Blob with JSON content and use /upload endpoint @@ -3455,4 +3487,4 @@ _C.addEventListener('touchstart', lock, false); _C.addEventListener('mouseout', move, false); _C.addEventListener('mouseup', move, false); -_C.addEventListener('touchend', move, false); +_C.addEventListener('touchend', move, false); \ No newline at end of file diff --git a/wled00/data/liveview.htm b/wled00/data/liveview.htm index 69cc20c543..6515f5d56a 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -1,5 +1,5 @@ - + diff --git a/wled00/data/liveviewws2D.htm b/wled00/data/liveviewws2D.htm index 91f63739cf..2b66b47724 100644 --- a/wled00/data/liveviewws2D.htm +++ b/wled00/data/liveviewws2D.htm @@ -1,5 +1,5 @@ - + diff --git a/wled00/data/msg.htm b/wled00/data/msg.htm index 2bb7e8825b..4bd58919a5 100644 --- a/wled00/data/msg.htm +++ b/wled00/data/msg.htm @@ -1,5 +1,5 @@ - + diff --git a/wled00/data/pixart/pixart.htm b/wled00/data/pixart/pixart.htm index c67ac46a55..8c0e9773d8 100644 --- a/wled00/data/pixart/pixart.htm +++ b/wled00/data/pixart/pixart.htm @@ -1,5 +1,5 @@ - + diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 86a1449754..5fb3bb0a1d 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -5,9 +5,7 @@ - WLED PixelForge - -
-
-
-
-
-

LED & Hardware setup

+ +
+
+
+
+

LED setup

+
Total LEDs: ?
Recommended power supply for brightest white:
?


+ Global brightness factor: %
Enable automatic brightness limiter:
Automatically limits brightness to stay close to the limit.
@@ -892,7 +1036,7 @@

LED & Hardware setup

If using multiple outputs it is recommended to use per-output limiter.
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.
Maximum PSU Current: mA
- Use per-output limiter:
+ Use per-output limiter:

-

Hardware setup

-
LED outputs:
+

LED outputs:



@@ -915,8 +1058,11 @@

Hardware setup

⚠ You might run into stability or lag issues.
Use less than 800 LEDs per output for the best experience!
+
-
Use parallel I2S:
+ Show Advanced Settings
Make a segment for each output:
Custom bus start indices:

@@ -927,17 +1073,45 @@

Hardware setup

-
+
+
+

Color & White

+ Use Gamma correction for color: (strongly recommended)
+ Use Gamma correction for brightness: (not recommended)
+ Use Gamma value:

+ White Balance correction:
+
+ Global override for Auto-calculate white: + +
+ Calculate CCT from RGB:
+ CCT IC used (Athom 15W):
+ CCT blending (±100%): %
+ Positive: additive blend, Negative: exclusive blend
+ Set to 0 when using 2-wire (reverse polarity) CCT strips

+
+
+

Hardware setup

+
+

Buttons

- Buttons:

Disable internal pull-up/down:
- Touch threshold:
-
+ Touch threshold:

+
+
+

IR Remote

IR GPIO:  ✕
Apply IR change to main segment only:
- IR info
-
+ IR info

+
+
+

Relay

Relay GPIO:  ✕
- Invert Open drain
-
-

Defaults

+ Invert Open drain

+
+

General settings

+
+

Power up

Turn LEDs on after power up/reset:
- Default brightness: (1-255)

- Apply preset at boot (0 uses values from above)

- Use Gamma correction for color: (strongly recommended)
- Use Gamma correction for brightness: (not recommended)
- Use Gamma value:

- Brightness factor: % + with brightness: (1-255)
+ (disable if using boot preset to turn LEDs on)

+ Apply preset at boot (0 = none)

+
+

Transitions

- Default transition time: ms
- Random Cycle Palette Time: s
+ Default transition time: ms

+
+
+

Random Palettes

+ Use harmonic colors in Random palettes:
+ Random Palette Cycle Time: s

+
+

Timed light

Default duration: min
Default target brightness:
@@ -977,24 +1160,9 @@

Timed light

-

White management

- White Balance correction:
-
- Global override for Auto-calculate white:
- -
- Calculate CCT from RGB:
- CCT IC used (Athom 15W):
- CCT additive blending: %
- WARNING: When using H-bridge for reverse polarity (2-wire) CCT LED strip
make sure this value is 0.
(ESP32 variants only, ESP8266 does not support H-bridges)
-
+

+
+

Advanced

Palette wrapping:
- Use harmonic Random Cycle palette:
Target refresh rate: FPS -
-
Config template:
-
- - -
+

+
+
Config template:
+
+ + +
diff --git a/wled00/data/settings_pin.htm b/wled00/data/settings_pin.htm index 22f94e3790..a6d8984c34 100644 --- a/wled00/data/settings_pin.htm +++ b/wled00/data/settings_pin.htm @@ -14,10 +14,12 @@
+

Please enter settings PIN code


+
\ No newline at end of file diff --git a/wled00/data/settings_pininfo.htm b/wled00/data/settings_pininfo.htm new file mode 100644 index 0000000000..7fe4e889bb --- /dev/null +++ b/wled00/data/settings_pininfo.htm @@ -0,0 +1,101 @@ + + + + + + Pin Info + + + + + +

Pin Info

+
Loading...
+ + + diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 182e729648..b52e9a15e0 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -3,7 +3,7 @@ - Misc Settings + Security & Update Setup -
-
-
-
-
-

Security & Update setup

+ +
+
+
+
+

Security & Update Setup

+
Settings PIN:
⚠ Unencrypted transmission. Be prudent when selecting PIN, do NOT use your banking, door, SIM, etc. pin!

Lock wireless (OTA) software update:
@@ -58,14 +59,16 @@

Security & Update setup

Factory reset:
All settings and presets will be erased.

⚠ Unencrypted transmission. An attacker on the same network can intercept form data!
-
+
+

Software Update


Enable ArduinoOTA:
Only allow update from same network/WiFi:
⚠ If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.
Disabling this option will make your device less secure.

-
+
+

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
@@ -74,14 +77,16 @@

Backup & Restore

Restore presets


Backup configuration
Restore configuration

-
+
+

About

WLED version ##VERSION##

Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
Licensed under the EUPL v1.2 license

- Installed version: WLED ##VERSION##
+ Installed version: WLED ##VERSION## +
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 73e4d9a268..97937f70b3 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -50,6 +50,10 @@ } function hideDMXInput(){gId("dmxInput").style.display="none";} function hideNoDMXInput(){gId("dmxInputOff").style.display="none";} + function hideNoDMXOutput(){ + gId("dmxOnOffOutput").style.display="none"; + gId("dmxOutput").style.display="inline"; + } @@ -59,15 +63,21 @@

Sync setup

+

WLED Broadcast

UDP Port:
2nd Port:
+
+
+

ESP-NOW

-ESP-NOW support is disabled.
+Disabled. Enable ESP-NOW in WiFi settings.
Use ESP-NOW sync:
(in AP mode or no WiFi)
+
+

Sync groups

@@ -106,9 +116,13 @@

Sync groups

+
+

Receive

Brightness, Color, Effects, and Palette
Segment options, bounds +
+

Send

Enable Sync on start:
Send notifications on direct change:
@@ -117,11 +131,13 @@

Send

Send Philips Hue change notifications:
UDP packet retransmissions:

Reboot required to apply changes. -
+
+

Instance List

Enable instance list:
Make this instance discoverable: -
+
+

Realtime

Receive UDP realtime:
Use main segment only:
@@ -166,14 +182,19 @@

Wired DMX Input Pins

DMX TX: DI
DMX Enable: RE+DE
DMX Port:
-
+
+
-
This firmware build does not include DMX Input support.
+
This firmware build does not include DMX Input support.
-
-
This firmware build does not include DMX output support.
+
+
This firmware build does not include DMX output support.
-
+
+

Alexa Voice Assistant

This firmware build does not include Alexa support.

@@ -183,13 +204,15 @@

Alexa Voice Assistant

Alexa invocation name:
Also emulate devices to call the first presets

-
-
MQTT and Hue sync all connect to external hosts!
+
+ +
MQTT and Hue sync connect to external hosts!
This may impact the responsiveness of WLED.

For best results, only use one of these services at a time.
(alternatively, connect a second ESP to them and use the UDP sync) -
+ +

MQTT

This firmware build does not include MQTT support.
@@ -209,6 +232,8 @@

MQTT

Retain brightness & color messages:
Reboot required to apply changes. MQTT info
+
+

Philips Hue

This firmware build does not include Philips Hue support.
@@ -226,6 +251,8 @@

Philips Hue

(when first connecting)
Hue status: Disabled in this build
+
+

Serial

This firmware build does not support Serial interface.
@@ -244,6 +271,7 @@

Serial


Keep at 115200 to use Improv. Some boards may not support high rates.
+

diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index 11f3c47d9d..b7848b862b 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -130,12 +130,13 @@ -
-
-
-
-
-

Time setup

+ +
+
+
+
+

Time setup

+
Get time from NTP server:

Use 24h format:
@@ -174,6 +175,8 @@

Time setup

(opens new tab, only works in browser)
+
+

Clock

Analog Clock overlay:
@@ -187,15 +190,22 @@

Clock

Countdown Goal:
Date: 20--
Time: ::
-

Macro presets

- Macros have moved!
- Presets now also can be used as macros to save both JSON and HTTP API commands.
- Just enter the preset ID below!
- Use 0 for the default action instead of a preset
- Alexa On/Off Preset:
+
+
+

Macro Presets

+ Presets can be used as macros for both JSON and HTTP API commands.
+ Enter the preset ID below.
+ Use 0 for the default action instead of a preset
+ JSON API
+ HTTP API
+
+

Timer & Alexa Presets

Countdown-Over Preset:
Timed-Light-Over Presets:
-

Button actions

+ Alexa On/Off Preset:
+
+
+

Button Action Presets

@@ -209,12 +219,15 @@

Button actions

Analog Button setup -

Time-controlled presets

+
+
+

Time-Controlled Presets

-
- - +
+
+ + diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm index 7955e8e699..d133aaf844 100644 --- a/wled00/data/settings_ui.htm +++ b/wled00/data/settings_ui.htm @@ -100,7 +100,7 @@ str += `${lb}:
`; } else if (t === 'string') { - str += `${lb}:

`; + str += `${lb}:

`; } } } @@ -219,25 +219,28 @@ -
-
-
-
- -
-
-

Web Setup

- Server description:
- + +
+
+
+ +
+
+

User Interface

+
+ Device Name:
Enable simplified UI:
+
+
The following UI customization settings are unique both to the WLED device and this browser.
You will need to set them again if using a different browser, device or WLED IP address.
Refresh the main UI to apply changes.

- +
Loading settings...
- +

UI Appearance

:
+ :
:
:
:
@@ -246,10 +249,13 @@

UI Appearance

:
:
:
- :
I hate dark mode:
:
+ :
+
Custom CSS:
+ +

UI Background

:
:
BG image:
@@ -265,10 +271,9 @@

Random BG image settings

:
- :
-
Custom CSS:
:
Holidays:
+


diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index dbef550115..bb2113ddbc 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -36,8 +36,9 @@ urows=""; if (isO(umCfg)) { for (const [k,o] of Object.entries(umCfg)) { - urows += `

${k}

`; + urows += `

${k}

`; addField(k,'unknown',o); + urows += `
`; } } if (urows==="") urows = "Usermods configuration not found.
Press Save to initialize defaults."; @@ -290,18 +291,20 @@

Usermod Setup

- Global I2C GPIOs (HW)
- (change requires reboot!)
- SDA: - SCL: -
- Global SPI GPIOs (HW)
- (only changable on ESP32, change requires reboot!)
- MOSI: - MISO: - SCLK: -
- Reboot after save?
+
+

Global I2C & SPI

+ + I2C GPIOs (HW)
+ SDA: + SCL:
+
SPI GPIOs (HW)
+ only changable on ESP32
+ MOSI: + MISO: + SCLK:
+
change requires reboot!
+ Reboot after save? +
Loading settings...

diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 65a04b9178..614d795329 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -118,11 +118,24 @@ gId("wifi_add").style.display = (i1) ? "inline":"none"; } - function addWiFi(ssid="",pass="",bssid="",ip=0,gw=0,sn=0x00ffffff) { // little endian + function addWiFi(ssid="",pass="",bssid="",ip=0,gw=0,sn=0x00ffffff,type=-1,anon="",ident="") { // little endian var i = gId("wifi_entries").childNodes.length; if (i >= maxNetworks) return; + var encryptionTypeField = ""; + if (type >=0 && type < 2) { + encryptionTypeField = `WiFi encryption type:
+
+
+Anonymous identity:

+Identity:

+
`; + } var b = `

Network name (SSID${i==0?", empty to not connect":""}):
0?"required":""}>
+${encryptionTypeField} Network password:

BSSID (optional):

Static IP (leave at 0.0.0.0 for DHCP)${i==0?"
Also used by Ethernet":""}:
@@ -185,31 +198,63 @@ rC++; gId('+').style.display = gId("rml").childElementCount < 10 ? 'inline' : 'none'; // can't append to list anymore, hide button } + function E(i) { + const select = gId("ET"+i); + if (select.value === "0") { + gId("IDS"+i).style.display = "none"; + } else { + gId("IDS"+i).style.display = ""; + } + } -
-
-
-
-
-

WiFi setup

-

Connect to existing network

+ +
+
+
+
+

WiFi & Network Settings

+
+

Wireless network


- Wireless networks


+
+
+

Ethernet Type

+

+
+
+

DNS & mDNS

DNS server address:
...

mDNS address (leave empty for no mDNS):
http:// .local
Client IP: Not connected
+
+

Configure Access Point

AP SSID (leave empty for no AP):

Hide AP name:
@@ -224,12 +269,14 @@

Configure Access Point


AP IP: Not active
-

Experimental

+
+
+

WiFi Power

Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
- Can help with connectivity issues and Audioreactive sync.
- Disabling WiFi sleep increases power consumption.

-
TX power: @@ -244,7 +291,8 @@

Experimental


WARNING: Modifying TX power may render device unreachable.
- +
+

ESP-NOW Wireless

This firmware build does not include ESP-NOW support.
@@ -261,28 +309,9 @@

ESP-NOW Wireless

- -
-

Ethernet Type

-

-
-
- - +
+
+ + diff --git a/wled00/data/style.css b/wled00/data/style.css index 059b8a5be6..69bc5aa96b 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -5,7 +5,7 @@ body { font-family: Verdana, sans-serif; font-size: 1rem; text-align: center; - background: #222; + background: #111; color: #fff; line-height: 200%; margin: 0; @@ -23,6 +23,13 @@ a, a:hover { color: #28f; text-decoration: none; } +.sec { + background: #222; + border-radius: 20px; + padding: 8px; + margin: 12px auto; + max-width: 520px; +} button, .btn { background: #333; color: #fff; diff --git a/wled00/data/update.htm b/wled00/data/update.htm index e93a113fae..5685b82b20 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -1,5 +1,5 @@ - + WLED Update @@ -19,21 +19,30 @@ } function GetV() { // Fetch device info via JSON API instead of compiling it in - fetch('/json/info') + fetch(getURL('/json/info')) .then(response => response.json()) .then(data => { document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`; document.querySelector('.release-name').textContent = data.release; - // TODO - assemble update URL + // assemble update URL and update release badge + if (data.repo && data.repo !== "unknown") { + const repoUrl = "https://github.com/" + data.repo + "/releases/latest"; + const badgeUrl = "https://img.shields.io/github/release/" + data.repo + ".svg?style=flat-square"; + document.querySelector('.release-repo').href = repoUrl; + document.querySelector('.release-badge').src = badgeUrl; + toggle('release-download'); // only show release download item after receiving a valid data.repo + } else { + gId('Norelease-download').classList.add("hide"); // repo invalid -> hide everything + } // TODO - can this be done at build time? if (data.arch == "esp8266") { toggle('rev'); } - const isESP32 = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2'); - if (isESP32) { - gId('bootloader-section').style.display = 'block'; + const allowBl = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2'); + if (allowBl) { + toggle('bootupd') if (data.bootloaderSHA256) { - gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + data.bootloaderSHA256; + gId('bootloader-hash').innerText = 'Current bootloader SHA256: \n' + data.bootloaderSHA256; } } }) @@ -42,40 +51,60 @@ // Fallback to compiled-in value if API call fails document.querySelector('.installed-version').textContent = 'Unknown'; document.querySelector('.release-name').textContent = 'Unknown'; + gId('Norelease-download').classList.add("hide"); // fetch failed -> hide everything }); } + function hideforms() { + gId('toprow').classList.add("hide"); // hide top row with "back" button + gId('backbtn').classList.add("hide"); // hide "back" button on bottom of the page + gId('bootupd').classList.add("hide"); + toggle('upd'); + } -

WLED Software Update

-
+
+
+ +
+
+ +
+

WLED Software Update

+

Installed version: Loading...
Release: Loading...
- Download the latest binary: Latest binary: Checking...
+ + Download the latest binary:
-
+ badge
+ +



-
-
-
- +
+ +
+
+
+
-