diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index e875ca18..d311af4a 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -50,10 +50,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.platformio - key: ${{ runner.os }}-${{ matrix.environment}}-${{ hashFiles('platformio.ini') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.environment}} - + key: ${{ runner.os }}-${{ matrix.environment}} - name: Set up Python uses: actions/setup-python@v5 with: @@ -82,15 +79,13 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - uses: actions/download-artifact@v4 - with: - name: firmware-release-* - name: List Artifacts - run: find ./ + run: find ./ -type f -name *.bin -exec mv -v {} ./ \; - name: Create draft release uses: softprops/action-gh-release@v1 with: draft: True files: | - *.bin + WLEDMM*.bin env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index cdfd81d0..5a928325 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,10 @@ esp01-update.sh platformio_override.ini replace_fs.py wled-update.sh +qodana.yaml +compile_commands.json +/build/ /build_output/ /node_modules/ diff --git a/Aircoookie_LICENSE b/Aircoookie_LICENSE deleted file mode 100644 index 30fd7534..00000000 --- a/Aircoookie_LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -The license below is valid for the code that is common with upstream "AC WLED" from https://github.com/Aircoookie/WLED. -Additions and code changes that are exclusive to this fork ("MoonModules WLED") are under GPLv3 license, see LICENSE. - -MIT License - -Copyright (c) 2016 Christian Schwinne - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/LICENSE b/LICENSE index f288702d..22953c3c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,291 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as +defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This + Licence does not define the extent of modification or dependence on the + Original Work required in order to classify a work as a Derivative Work; + this extent is determined by copyright law applicable in the country + mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which + is meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under + the Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright +vested in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case + may be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make +effective the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights +to any patents held by the Licensor, to the extent necessary to make use of +the rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, +in a notice following the copyright notice attached to the Work, a repository +where the Source Code is easily and freely accessible for as long as the +Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits +from any exception or limitation to the exclusive rights of the rights owners +in the Work, of the exhaustion of those rights or of other applicable +limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and +a copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of +the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions +on the Work or Derivative Work that alter or restrict the terms of the +Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed +under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, +‘Compatible Licence’ refers to the licences listed in the appendix attached to +this Licence. Should the Licensee's obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade +names, trademarks, service marks, or names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she +brings to the Work are owned by him/her or licensed to him/her and that he/she +has the power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ +basis and without warranties of any kind concerning the Work, including +without limitation merchantability, fitness for a particular purpose, absence +of defects or errors, accuracy, non-infringement of intellectual property +rights other than copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a +condition for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the +use of the Work, including without limitation, damages for loss of goodwill, +work stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional +agreement, defining obligations or services consistent with this Licence. +However, if accepting obligations, You may act only on your own behalf and on +your sole responsibility, not on behalf of the original Licensor or any other +Contributor, and only if You agree to indemnify, defend, and hold each +Contributor harmless for any liability incurred by, or claims asserted against +such Contributor by the fact You have accepted any warranty or additional +liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I +agree’ placed under the bottom of a window displaying the text of this Licence +or by affirming consent in any other similar way, in accordance with the rules +of applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this +Licence, such as the use of the Work, the creation by You of a Derivative Work +or the Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions +of this Licence or updated versions of the Appendix, so far this is required +and reasonable, without reducing the scope of the rights granted by the +Licence. New versions of the Licence will be published with a unique version +number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty + on the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive + jurisdiction of the competent court where the Licensor resides or conducts + its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the +above licences without producing a new version of the EUPL, as long as they +provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a +new EUPL version. diff --git a/boards/lilygo-t7-s3.json b/boards/lilygo-t7-s3.json new file mode 100644 index 00000000..4bf071fc --- /dev/null +++ b/boards/lilygo-t7-s3.json @@ -0,0 +1,47 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_TTGO_T7_S3", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "LILYGO T3-S3", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.aliexpress.us/item/3256804591247074.html", + "vendor": "LILYGO" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aa060721..59697131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "wled", "version": "0.14.1-b32.41.dev", - "license": "GPL-3.0-or-later", + "license": "EUPL-1.2", "dependencies": { "clean-css": "^4.2.3", "html-minifier-terser": "^5.1.1", diff --git a/package.json b/package.json index c73dd26f..b6763b33 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "git+https://github.com/MoonModules/WLED.git" }, "author": "", - "license": "GPL-3.0-or-later", + "license": "EUPL-1.2", "bugs": { "url": "https://github.com/MoonModules/WLED/issues" }, diff --git a/platformio.ini b/platformio.ini index b5b93187..b1ce10e2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,8 +78,11 @@ default_envs = esp32S3_4MB_PSRAM_S ;; for lolin s3 mini, S3 zero, S3 super mini - optimized for speed esp32S3_4MB_PSRAM_M ;; for lolin s3 mini, S3 zero, S3 super mini esp32S3_8MB_PSRAM_M ;; experiemental + esp32S3_16MB_PSRAM_M_HUB75 ;; for S3 with 16MB flash, and MOONHUB HUB75 adapter board + esp32S3_WROOM-2_M ;; for S3 WROOM-2 ;; esp32s2_tinyUF2_PSRAM_S ;; experimental - only for adafruit -S2 boards with tinyUF2 bootloader !!! - esp32s2_PSRAM_M ;; experimental + esp32s2_PSRAM_S ;; OTA-compatible with upstream + esp32s2_PSRAM_M ;; for lolin S2 mini esp32c3dev_4MB_M ;; experimental esp32c3dev_2MB_M ;; experimental - 2MB Flash, no OTA esp32c3mini_dio_4MB_M ;; for boards that need "dio" flash mode (instead of qio) @@ -248,7 +251,7 @@ lib_deps = ;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections ;;https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 ;; https://github.com/lost-hope/ESPAsyncWebServer.git#master ;; WLEDMM to display .log and .wled files in /edit - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 ;; newer with bugfixes and stability improvements + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 ;; newer with bugfixes and stability improvements #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following @@ -288,6 +291,7 @@ build_flags = ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown -D USERMOD_AUDIOREACTIVE + -D NON32XFER_HANDLER ;; ask forgiveness for PROGMEM misuse lib_deps = #https://github.com/lorol/LITTLEFS.git @@ -297,6 +301,37 @@ lib_deps = makuna/NeoPixelBus @ 2.7.5 ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + +;; experimental - for using older NeoPixelBus 2.7.9 +lib_deps_compat = + ESPAsyncTCP @ 1.2.2 + ESPAsyncUDP + ESP8266PWM + fastled/FastLED @ 3.6.0 + IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.9 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + + [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 @@ -700,6 +735,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} board_build.partitions = ${esp32.large_partitions} @@ -796,6 +832,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DARDUINO_USB_DFU_ON_BOOT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 6792 bytes FLASH -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -1022,11 +1059,11 @@ build_disable_sync_interfaces = -D WLED_DISABLE_ADALIGHT ;; WLEDMM this board does not have a serial-to-USB chip. Better to disable serial protocols, to avoid crashes (see upstream #3128) -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only -AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT ;; WLEDMM audioreactive usermod, licensed under GPLv3 +AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT ;; WLEDMM audioreactive usermod, licensed under EUPL-1.2 AR_lib_deps = https://github.com/softhack007/arduinoFFT.git#develop @ 1.9.2 ;; used for USERMOD_AUDIOREACTIVE - optimized version, 10% faster on -S2/-C3 animartrix_build_flags = -D USERMOD_ANIMARTRIX ;; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick -animartrix_lib_deps = https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7 +animartrix_lib_deps = https://github.com/netmindz/animartrix.git#657f754783268b648e1d56b3cd31c810379d0c89 ;; Dirty state fix animartrix_lib_ignore = animartrix ;; to remove the animartrix lib dependancy (saves a few bytes) DMXin_build_flags = -D WLED_ENABLE_DMX_INPUT ;; WLEDMM DMX physical input - requires ESP-IDF v4.4.x @@ -1040,10 +1077,9 @@ HUB75_build_flags = -D NO_FAST_FUNCTIONS ;; If you are not using AdafruitGFX than you probably do not need this either, save memory/code size -D NO_CIE1931 ;; Do not use LED brightness compensation described in CIE 1931. We use FastLED dimming already -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips -;; HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git @ 3.0.11 ;; breaks the build (2024-07-30) -;; HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#1e4c80a26454aca7b8129bd5a966b0af329d2703 ;; 3.0.10 - something strange is going on here ... -;; HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#1e4c80a26454aca7b8129bd5a966b0af329d2703 ;; 3.0.10 - something strange is going on here ... -HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#c4ecdcfeeb5aa668d92ddf3c3c74bc93316f6e10 ;; 3.0.11 + -D WLEDMM_SLOWPATH ;; WLEDMM: do not use I2S for driving ws2812 LEDs (HUB75 driver needs I2S#1) +;; HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a9 ;; S3_LCD_DIV_NUM fix +HUB75_lib_deps = https://github.com/softhack007/ESP32-HUB75-MatrixPanel-DMA_sh7.git#fix_dangling_pointer ;; S3 bugfix for crash in ~MatrixPanel_I2S_DMA() HUB75_lib_ignore = ESP32 HUB75 LED MATRIX PANEL DMA Display ;; to remove the HUB75 lib dependancy (saves a few bytes) NetDebug_build_flags = @@ -1240,9 +1276,12 @@ build_flags = build_flags = -O2 ;; optimize for performance instead of size ;-ffast-math ;; gives a few (2-5) percent speedup on ESP32-S3, but causes slight slowdown on classic ESP32 - -mtarget-align -free -fipa-pta ;; these are very useful, too + -funsafe-math-optimizations ;; less dangerous than -ffast-math; still allows the compiler to exploit FMA and reciprocals (10% faster on -S3) + ;; -mtarget-align ;; this one is included in -O2, so its not necessary to explicitly add it + -free -fipa-pta ;; these are very useful, too -fno-jump-tables -fno-tree-switch-conversion ;; needed -freorder-blocks -Wwrite-strings -fstrict-volatile-bitfields ;; needed +build_flags_V4 = ${Speed_Flags.build_flags} -fsingle-precision-constant ;; old framework does not like this flag build_unflags = -Os ;; to disable standard optimization for size @@ -1573,9 +1612,12 @@ build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_RELEASE_NAME=esp01_1MB_S -D WLED_DISABLE_ALEXA -D WLED_DISABLE_HUESYNC + -D WLED_DISABLE_ESPNOW ;; exceeds flash size limits + -D WLED_DISABLE_INFRARED ;; exceeds flash size limits lib_deps = ${esp8266.lib_deps} -; RAM: [====== ] 59.5% (used 48748 bytes from 81920 bytes) -; Flash: [========= ] 90.7% (used 809992 bytes from 892912 bytes) +lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation +; RAM: [====== ] 60.6% (used 49616 bytes from 81920 bytes) +; Flash: [==========] 99.8% (used 890835 bytes from 892912 bytes) # ------------------------------------------------------------------------------ @@ -1592,6 +1634,7 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH -D WLED_DISABLE_LOXONE -D WLED_DISABLE_ALEXA -D WLED_DISABLE_HUESYNC @@ -1627,7 +1670,7 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} ;; -Wall -Wextra -Wno-unused-value -Wno-format -Wno-type-limits -D WLED_RELEASE_NAME=esp32_4MB_V4_HUB75 - ${Speed_Flags.build_flags} ;; -O2 -> optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; -O2 -> optimize for speed instead of size ;; -D DEBUG -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup @@ -1699,7 +1742,7 @@ extends = esp32_4MB_V4_S_base build_unflags = ${esp32_4MB_V4_S_base.build_unflags} ${Speed_Flags.build_unflags} ;; to override -Os build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLED_RELEASE_NAME=esp32_16MB_V4_S -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup @@ -1769,7 +1812,9 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ; RAM 116 bytes; FLASH 13524 bytes @@ -1802,9 +1847,11 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -DARDUINO_EVENT_RUNNING_CORE=0 ;; assign Wifi to core0, to have more CPU on core#1 (arduino loop) -DARDUINO_RUNNING_CORE=1 ;; should be default, but does not hurt -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - seems to move more buffers into PSRAM - ;;${Speed_Flags.build_flags} ;; optimize for speed instead of size --> over 100% flash, but works with 256KB filesystem (alternative partitions file) + ;;${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size --> over 100% flash, but works with 256KB filesystem (alternative partitions file) -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap ;;-D CONFIG_ESP32_REV_MIN=3 ;; disables PSRAM bug workarounds in the core, reducing the code size and improving overall performance. -D WLED_RELEASE_NAME=esp32_4MB_PSRAM_REV3_S -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET @@ -1834,7 +1881,9 @@ build_flags = ${esp32_4MB_V4_M_base.esp32_build_flags} -D WLED_RELEASE_NAME=esp32_4MB_PSRAM_M -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ;; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ;; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ;; RAM 116 bytes; FLASH 13524 bytes @@ -1874,6 +1923,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 ${common_mm.build_disable_sync_interfaces} -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH ; -D WLED_DEBUG ; -D SR_DEBUG lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} @@ -1896,6 +1946,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden ${common_mm.HUB75_build_flags} ${common_mm.animartrix_build_flags} ; -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON -D WLED_USE_PSRAM ;; un-comment in case your board supports PSRAM + ;; -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_RELEASE_NAME=esp32S3_8MB_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode @@ -1943,7 +1994,9 @@ board_build.flash_mode = qio ;; use "dio" if your board gets unstable with "qio" build_unflags = ${env:esp32S3_8MB_M.build_unflags} ;; use the same as "normal" S3 buildenv build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + ${common_mm.HUB75_build_flags} + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_RELEASE_NAME=esp32S3_8MB_PSRAM_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode @@ -1967,10 +2020,12 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden ; -D SR_DEBUG ; -D MIC_LOGGER lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation board_build.partitions = tools/WLED_ESP32_8MB.csv -; RAM: [==== ] 36.3% (used 118828 bytes from 327680 bytes) -; Flash: [======= ] 70.7% (used 1483465 bytes from 2097152 bytes) +; RAM: [== ] 21.1% (used 69156 bytes from 327680 bytes) +; Flash: [======== ] 75.9% (used 1591817 bytes from 2097152 bytes) ;; MM for ESP32-S3 boards - FASTPATH + optimize for speed [env:esp32S3_8MB_S] @@ -1985,7 +2040,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode ;;-D WLED_DISABLE_ADALIGHT ;; disables serial protocols - recommended for Hardware-CDC USB (Serial RX will receive junk commands when RX pin is unconnected, unless its pulled down by resistor) - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. ${common_mm.animartrix_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -2018,6 +2073,89 @@ board_build.partitions = tools/WLED_ESP32_8MB.csv ; RAM: [=== ] 25.8% (used 84544 bytes from 327680 bytes) ; Flash: [======== ] 78.1% (used 1638737 bytes from 2097152 bytes) + +;; for S3 with 16MB flash, octal PSRAM, +;; MOONHUB HUB75 adapter board +[env:esp32S3_16MB_PSRAM_M_HUB75] +extends = env:esp32S3_8MB_PSRAM_M +board = lilygo-t7-s3 +board_build.arduino.memory_type = qio_opi +board_build.flash_mode = qio + +build_unflags = ${env:esp32S3_8MB_PSRAM_M.build_unflags} + -D WLED_RELEASE_NAME=esp32S3_8MB_M + -D LEDPIN=21 + ${Speed_Flags.build_unflags} ;; to override -Os + +build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation + ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${Speed_Flags.build_flags_V4} ;; optimize for speed + ${common_mm.HUB75_build_flags} + -D MOONHUB_S3_PINOUT ;; HUB75 pinout + ${common_mm.animartrix_build_flags} + -D WLED_RELEASE_NAME=esp32S3_16MB_PSRAM_M_HUB75 + -D WLEDMM_FASTPATH + -D WLED_DISABLE_BROWNOUT_DET + ;; -D JSON_BUFFER_SIZE=18432 ;; default 54000 + -D MIN_HEAP_SIZE=6144 ;; default 8192 + -D MAX_SEGMENT_DATA=40960 ;; default 32767 + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip + ;; -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode + -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 + ;; -DUSERMOD_BATTERY_MEASUREMENT_PIN=2 ;; battery voltage pin + ;; -D STATUSLED=17 ;; onboard LED + -D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75 + -D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic + +lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} +;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size =16777216 +; RAM: [== ] 19.0% (used 62332 bytes from 327680 bytes) +; Flash: [====== ] 57.7% (used 1813585 bytes from 3145728 bytes) + + +;; MM for ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +;; includes HUB75 and AnimArtix +[env:esp32S3_WROOM-2_M] +extends = env:esp32S3_8MB_PSRAM_M +board_build.flash_mode = dout ;; dummy value - bootloader will switch to "opi" +board_build.arduino.memory_type = opi_opi +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_upload.flash_size = 16MB +board_upload.maximum_size =16777216 +board_build.partitions = ${esp32.extreme_partitions} +build_unflags = ${env:esp32S3_8MB_PSRAM_M.build_unflags} + -D WLED_RELEASE_NAME=esp32S3_8MB_M + -D LEDPIN=21 + ${Speed_Flags.build_unflags} ;; to override -Os +build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation + ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${Speed_Flags.build_flags_V4} ;; optimize for speed + ${common_mm.HUB75_build_flags} + ${common_mm.animartrix_build_flags} + -D WLED_RELEASE_NAME=esp32S3_WROOM-2_M + -D WLEDMM_FASTPATH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip + ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode + -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 + ;; -D LEDPIN=38 ;; buildin LED + ;; -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D LEDPIN=12 -D BTNPIN=0 -D RLYPIN=-1 -D IRPIN=-1 -D AUDIOPIN=-1 ;; to avoid pin conflict with HUB75 + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} + ;; MM for esp32-s3 zero/supermini and lolin S3 mini boards - fastpath, optimize for speed [env:esp32S3_4MB_PSRAM_S] extends = env:esp32S3_8MB_S @@ -2026,15 +2164,17 @@ board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, ;; board_build.partitions = tools/WLED_ESP32_4MB_512KB_FS.csv ;; 1.7MB firmware, 500KB filesystem (esptool erase_flash needed when changing from "standard WLED" partitions) build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} - -D WLED_RELEASE_NAME=esp32S3_4MB_S - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -D WLED_RELEASE_NAME=esp32S3_4MB_PSRAM_S + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - allows some buffers to use PSRAM -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode -D WLED_DISABLE_ADALIGHT ;; disables serial protocols - recommended for Hardware-CDC USB (Serial RX will receive junk commands when RX pin is unconnected, unless its pulled down by resistor) - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLEDMM_FASTPATH ;; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH ${common_mm.animartrix_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_DISABLE_LOXONE ; FLASH 1272 bytes @@ -2062,8 +2202,9 @@ board = lolin_s3_mini ;; -S3 mini: 4MB flash 2MB PSRAM board_build.partitions = ${esp32.default_partitions} build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} - -D WLED_RELEASE_NAME=esp32S3_4MB_M - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -D WLED_RELEASE_NAME=esp32S3_4MB_PSRAM_M + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - allows some buffers to use PSRAM -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Serial-to-USB chip @@ -2126,7 +2267,8 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_DISABLE_ADALIGHT ;; disables serial protocols when using CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -D SERVERNAME='"WLED-S2"' - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ;; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ;; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ;; RAM 116 bytes; FLASH 13524 bytes @@ -2161,7 +2303,7 @@ platform = ${esp32s2.platform} ;; using 5.2.0, due to platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini -board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem (esptool erase_flash needed when changing from "standard WLED" partitions) +board_build.partitions = ${esp32.extended_partitions} ;; 1.65MB firmware, 700KB filesystem (esptool erase_flash needed when changing from "standard WLED" partitions) board_build.flash_mode = dio upload_speed = 256000 ;; 921600 build_unflags = ${common.build_unflags} @@ -2170,7 +2312,6 @@ build_unflags = ${common.build_unflags} -D USERMOD_DALLASTEMPERATURE ;; disabled because it hangs during usermod setup on -S3 (autodetect broken?) -D WLED_ENABLE_DMX ;; disabled because it does not work with ESP-IDF 4.4.x (buggy driver in SparkFunDMX) -D WLED_ENABLE_DMX_INPUT ;; needs more testing - -DWLEDMM_FASTPATH ;; needs more testing on -S2 -D WLED_ENABLE_HUB75MATRIX build_flags = ${common.build_flags} ${esp32s2.build_flags} ;; ${Debug_Flags.build_flags} @@ -2178,7 +2319,8 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} ${common_mm.build_flags_S} ${common_mm.build_flags_M} -Wno-misleading-indentation -Wno-format-truncation -D WLED_RELEASE_NAME=esp32s2_4MB_M - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 -D WLED_DISABLE_ADALIGHT ;; disables serial protocols, as the board only has CDC USB @@ -2201,9 +2343,32 @@ lib_deps = ${esp32s2.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE + ${common_mm.HUB75_lib_ignore} + ${common_mm.DMXin_lib_ignore} monitor_filters = esp32_exception_decoder -; RAM: [== ] 21.8% (used 71304 bytes from 327680 bytes) -; Flash: [======== ] 84.0% (used 1596970 bytes from 1900544 bytes) +; RAM: [== ] 20.5% (used 67256 bytes from 327680 bytes) +; Flash: [========= ] 93.3% (used 1590266 bytes from 1703936 bytes) + +[env:esp32s2_PSRAM_S] +extends = env:esp32s2_PSRAM_M +board_build.partitions = ${esp32.default_partitions} ;; 1.55MB firmware, 1MB filesystem +build_unflags = ${env:esp32s2_PSRAM_M.build_unflags} + -DWLED_DISABLE_ADALIGHT + -D WLED_RELEASE_NAME=esp32s2_4MB_M + -DUSE_ALT_DISPLAY + -DUSERMOD_FOUR_LINE_DISPLAY + -DUSERMOD_ROTARY_ENCODER_UI + -DUSERMOD_ANIMARTRIX + ;; -DUSERMOD_ARTIFX ;; uncomment to reduce flash size +build_flags = ${env:esp32s2_PSRAM_M.build_flags} + -D WLED_RELEASE_NAME=esp32s2_4MB_S +lib_deps = ${env:esp32s2_PSRAM_M.lib_deps} +lib_ignore = ${env:esp32s2_PSRAM_M.lib_ignore} + U8g2 + ${common_mm.animartrix_lib_ignore} +; RAM: [== ] 20.4% (used 66792 bytes from 327680 bytes) +; Flash: [========= ] 94.8% (used 1490390 bytes from 1572864 bytes) + # ------------------------------------------------------------------------------ # esp32-C3 environments @@ -2231,6 +2396,7 @@ build_unflags = ${common.build_unflags} -D WLED_ENABLE_DMX_INPUT ;; needs more testing ;-D WLED_DEBUG_HOST='"192.168.x.x"' ;; to disable net print -D USERMOD_ANIMARTRIX ;; Tips our memory usage over the limit + -DUSERMOD_ARTIFX ;; over the limit -DWLEDMM_FASTPATH ;; needs more testing on -C3 build_flags = ${common.build_flags} ${esp32c3.build_flags} @@ -2261,9 +2427,8 @@ lib_ignore = ;IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE U8g2 ; not needed as we don't include USERMOD_FOUR_LINE_DISPLAY -; RAM: [=== ] 25.9% (used 84884 bytes from 327680 bytes) -; Flash: [==========] 98.9% (used 1555608 bytes from 1572864 bytes) - +; RAM: [== ] 24.6% (used 80732 bytes from 327680 bytes) +; Flash: [==========] 97.5% (used 1533360 bytes from 1572864 bytes) ;; MM environment for ESP32-C3 "mini" and "super mini" -> flash mode "dio" instead of "qio" (see #101) [env:esp32c3mini_dio_4MB_M] extends = env:esp32c3dev_4MB_M @@ -2280,8 +2445,8 @@ build_flags = ${env:esp32c3dev_4MB_M.build_flags} -D WLED_DISABLE_BROWNOUT_DET ;; the board only has a 500mA LDO, better to disable brownout detection -D WLED_DISABLE_ADALIGHT ;; to disable serial protocols for boards with CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -D HW_PIN_SDA=0 -D HW_PIN_SCL=1 ;; avoid pin conflicts -; RAM: [=== ] 25.8% (used 84700 bytes from 327680 bytes) -; Flash: [==========] 98.7% (used 1552582 bytes from 1572864 bytes) +; RAM: [== ] 24.6% (used 80556 bytes from 327680 bytes) +; Flash: [==========] 97.3% (used 1530346 bytes from 1572864 bytes) [env:esp32c3dev_2MB_M] extends = env:esp32c3dev_4MB_M @@ -2307,8 +2472,8 @@ build_flags = ${env:esp32c3dev_4MB_M.build_flags} -D WLED_DISABLE_ADALIGHT ;; to disable serial protocols for boards with CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -D HW_PIN_SDA=0 -D HW_PIN_SCL=1 ;; avoid pin conflicts -; RAM: [=== ] 25.3% (used 82828 bytes from 327680 bytes) -; Flash: [==========] 97.9% (used 1540138 bytes from 1572864 bytes) +; RAM: [== ] 24.0% (used 78676 bytes from 327680 bytes) +; Flash: [==========] 96.4% (used 1516068 bytes from 1572864 bytes) ;; MM environment for "seeed xiao -C3" boards [env:seeed_esp32c3_4MB_S] @@ -2342,9 +2507,8 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} lib_deps = ${esp32c3.lib_deps} ${common_mm.lib_deps_S} lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation monitor_filters = esp32_exception_decoder -; RAM: [== ] 23.6% (used 77460 bytes from 327680 bytes) -; Flash: [========= ] 91.7% (used 1442092 bytes from 1572864 bytes) - +; RAM: [== ] 22.4% (used 73388 bytes from 327680 bytes) +; Flash: [========= ] 92.7% (used 1458598 bytes from 1572864 bytes) # ------------------------------------------------------------------------------ # custom board environments # ------------------------------------------------------------------------------ @@ -2624,8 +2788,10 @@ board_build.flash_mode = qio build_unflags = ${env:esp32S3_8MB_M.build_unflags} ;; use the same as "normal" S3 buildenv -D ARDUINO_USB_CDC_ON_BOOT=1 ;; fix warning: "ARDUINO_USB_CDC_ON_BOOT" redefined; comment out for Serial debug + ${Speed_Flags.build_unflags} ;; to override -Os build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} + ${Speed_Flags.build_flags_V4} ;; -O2 -> optimize for speed instead of size -D WLED_RELEASE_NAME=matrixportal_esp32s3 -D SERVERNAME='"WLED-MatrixPortalS3"' ; Serial debug enabled -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode @@ -2636,6 +2802,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -D LOLIN_WIFI_FIX ;; try this in case Wifi does not work -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + ; -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - seems to move more buffers into PSRAM ${common_mm.HUB75_build_flags} -D DEFAULT_LED_TYPE=101 lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ;; ;; do not include ${esp32.lib_depsV4} !!!! diff --git a/readme.md b/readme.md index c1d5efab..83c488e4 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,10 @@ More info here: HTML tutorial Donations will be used to buy WLED related hardware, software or drinks shared with the contributors of this repo. +## License +WLED-MM is licensed under the [EUPL-1.2](https://joinup.ec.europa.eu/collection/eupl) or later. +The official license text is [available in 23 languages](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12). + ## Contributing We welcome contributions to this project! See [contributing](https://github.com/MoonModules/WLED/blob/mdev/CONTRIBUTING.md) for more information. > We would like to have this repository in a polite and friendly atmosphere, so please be kind and respectful to others. For more details, look at [Code of Conduct](https://github.com/MoonModules/WLED/blob/mdev/CODE_OF_CONDUCT.md). diff --git a/tools/ESP32-Chip_info.hpp b/tools/ESP32-Chip_info.hpp index 417ee449..f9c5ebec 100644 --- a/tools/ESP32-Chip_info.hpp +++ b/tools/ESP32-Chip_info.hpp @@ -469,7 +469,7 @@ void my_verbose_print_reset_reason(int reason) #endif /* - * parts below were created by softhack007, licenced under GPL v3.0 + * parts below were created by softhack007, licenced under EUPL-1.2 */ void show_psram_info_part1(void) diff --git a/usermods/Analog_Clock/Analog_Clock.h b/usermods/Analog_Clock/Analog_Clock.h index 596f0acb..9d82f767 100644 --- a/usermods/Analog_Clock/Analog_Clock.h +++ b/usermods/Analog_Clock/Analog_Clock.h @@ -102,9 +102,9 @@ private: void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { uint32_t ms = time.ms % 1000; - uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2; + uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); - uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2; + uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); } diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 5ec0f837..1e5f9681 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -6,16 +6,8 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license Licensed under the EUPL-1.2 or later - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - - WLED-MM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied - warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with WLED-MM. If not, see . */ @@ -199,7 +191,6 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea // use audio source class (ESP32 specific) #include "audio_source.h" -constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) // globals @@ -285,7 +276,7 @@ static volatile float micReal_max2 = 0.0f; // MicIn data max afte // some prototypes, to ensure consistent interfaces static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float static float fftAddAvg(int from, int to); // average of several FFT result bins -void FFTcode(void * parameter) __attribute__((noreturn)); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels, bool i2sFastpath); // post-processing and post-amp of GEQ channels @@ -394,11 +385,11 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningful part of FFT result #define LOG_256 5.54517744f // log(256) // These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins -static float vImag[samplesFFT] = {0.0f}; // imaginary parts +static float* vReal = nullptr; // FFT sample inputs / freq output - these are our raw result bins +static float* vImag = nullptr; // imaginary parts #ifdef FFT_MAJORPEAK_HUMAN_EAR -static float pinkFactors[samplesFFT] = {0.0f}; // "pink noise" correction factors +static float* pinkFactors = nullptr; // "pink noise" correction factors constexpr float pinkcenter = 23.66; // sqrt(560) - center freq for scaling is 560 hz. constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range of each FFT result bin #endif @@ -415,15 +406,6 @@ constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range o #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 #include -#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 - // arduinoFFT 2.x has a slightly different API - static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); -#else - // recommended version optimized by @softhack007 (API version 1.9) - static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); -#endif - // Helper functions // float version of map() @@ -461,6 +443,30 @@ constexpr bool skipSecondFFT = true; constexpr bool skipSecondFFT = false; #endif +// allocate FFT sample buffers from heap +static bool alocateFFTBuffers(void) { + #ifdef SR_DEBUG + USER_PRINT(F("\nFree heap ")); USER_PRINTLN(ESP.getFreeHeap()); + #endif + + if (vReal) free(vReal); // should not happen + if (vImag) free(vImag); // should not happen + if ((vReal = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; // calloc or die + if ((vImag = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; +#ifdef FFT_MAJORPEAK_HUMAN_EAR + if (pinkFactors) free(pinkFactors); + if ((pinkFactors = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; +#endif + + #ifdef SR_DEBUG + USER_PRINTLN("\nalocateFFTBuffers() completed successfully."); + USER_PRINT(F("Free heap: ")); USER_PRINTLN(ESP.getFreeHeap()); + USER_PRINT("FFTtask free stack: "); USER_PRINTLN(uxTaskGetStackHighWaterMark(NULL)); + USER_FLUSH(); + #endif + return(true); // success +} + // High-Pass "DC blocker" filter // see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) { @@ -497,9 +503,30 @@ void FFTcode(void * parameter) static bool isFirstRun = false; #ifdef FFT_USE_SLIDING_WINDOW - static float oldSamples[samplesFFT_2] = {0.0f}; // previous 50% of samples + static float* oldSamples = nullptr; // previous 50% of samples static bool haveOldSamples = false; // for sliding window FFT bool usingOldSamples = false; + if (!oldSamples) oldSamples = (float*) calloc(sizeof(float), samplesFFT_2); // allocate on first run + if (!oldSamples) { disableSoundProcessing = true; return; } // no memory -> die +#endif + + bool success = true; + if ((vReal == nullptr) || (vImag == nullptr)) success = alocateFFTBuffers(); // allocate sample buffers on first run + if (success == false) { disableSoundProcessing = true; return; } // no memory -> die + + // create FFT object - we have to do if after allocating buffers +#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 + // arduinoFFT 2.x has a slightly different API + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); +#else + // recommended version optimized by @softhack007 (API version 1.9) + #if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32) + static float* windowWeighingFactors = nullptr; + if (!windowWeighingFactors) windowWeighingFactors = (float*) calloc(sizeof(float), samplesFFT); // cache for FFT windowing factors - use heap + #else + static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM + #endif + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); #endif #ifdef FFT_MAJORPEAK_HUMAN_EAR @@ -543,7 +570,7 @@ void FFTcode(void * parameter) #endif // get a fresh batch of samples from I2S - memset(vReal, 0, sizeof(vReal)); // start clean + memset(vReal, 0, sizeof(float) * samplesFFT); // start clean #ifdef FFT_USE_SLIDING_WINDOW uint16_t readOffset; if (haveOldSamples && (doSlidingFFT > 0)) { @@ -636,7 +663,7 @@ void FFTcode(void * parameter) #endif // set imaginary parts to 0 - memset(vImag, 0, sizeof(vImag)); + memset(vImag, 0, sizeof(float) * samplesFFT); #ifdef FFT_USE_SLIDING_WINDOW memcpy(oldSamples, vReal+samplesFFT_2, sizeof(float) * samplesFFT_2); // copy last 50% to buffer (for sliding window FFT) @@ -763,14 +790,14 @@ void FFTcode(void * parameter) FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42 * (FFT_MajorPeak - FFT_MajPeakSmth); // I like this "swooping peak" look } else { // skip second run --> clear fft results, keep peaks - memset(vReal, 0, sizeof(vReal)); + memset(vReal, 0, sizeof(float) * samplesFFT); } #if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS) haveDoneFFT = true; #endif } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. - memset(vReal, 0, sizeof(vReal)); + memset(vReal, 0, sizeof(float) * samplesFFT); FFT_MajorPeak = 1; FFT_Magnitude = 0.001; } @@ -1083,6 +1110,11 @@ class AudioReactive : public Usermod { private: #ifdef ARDUINO_ARCH_ESP32 +// HUB75 workaround - audio receive only +#ifdef WLED_ENABLE_HUB75MATRIX +#undef SR_DMTYPE +#define SR_DMTYPE 254 // "network receive only" +#endif #ifndef AUDIOPIN int8_t audioPin = -1; #else @@ -1907,12 +1939,14 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 - // Reset I2S peripheral for good measure + // Reset I2S peripheral for good measure - not needed in esp-idf v4.4.x and later. + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0) i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed #if !defined(CONFIG_IDF_TARGET_ESP32C3) delay(100); periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 #endif + #endif delay(100); // Give that poor microphone some time to setup. #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -2022,10 +2056,27 @@ class AudioReactive : public Usermod { if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; if (i2c_sda >= 0) sdaPin = -1; // -1 = use global if (i2c_scl >= 0) sclPin = -1; - + case 9: + DEBUGSR_PRINTLN(F("AR: ES8311 Source (Mic)")); + audioSource = new ES8311Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f); + //useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour + delay(100); + // WLEDMM align global pins + if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined) + if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; + case 255: // falls through + case 254: // dummy "network receive only" driver + if (audioSource) delete audioSource; + audioSource = nullptr; + disableSoundProcessing = true; + audioSyncEnabled = AUDIOSYNC_REC; // force udp sound receive mode + break; + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // ADC over I2S is only possible on "classic" ESP32 case 0: @@ -2040,18 +2091,16 @@ class AudioReactive : public Usermod { } delay(250); // give microphone enough time to initialise - if (!audioSource) enabled = false; // audio failed to initialise + if (!audioSource && (dmType < 254)) enabled = false; // audio failed to initialise #endif - if (enabled) onUpdateBegin(false); // create FFT task, and initialize network + if (enabled) onUpdateBegin(false); // create FFT task, and initialize network #ifdef ARDUINO_ARCH_ESP32 - if (FFT_Task == nullptr) enabled = false; // FFT task creation failed + if (audioSource && FFT_Task == nullptr) enabled = false; // FFT task creation failed if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync - #ifdef WLED_DEBUG - DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #else - USER_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #endif + + if (dmType < 254) { USER_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));} + else { USER_PRINTLN(F("AR: No sound input driver configured - network receive only."));} disableSoundProcessing = true; } else { USER_PRINTLN(F("AR: sound input driver initialized successfully.")); @@ -2191,7 +2240,7 @@ class AudioReactive : public Usermod { if (audioSyncEnabled == AUDIOSYNC_REC) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled == AUDIOSYNC_SEND) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode #ifdef ARDUINO_ARCH_ESP32 - if (!audioSource->isInitialized()) { // no audio source + if (!audioSource || !audioSource->isInitialized()) { // no audio source disableSoundProcessing = true; if (audioSyncEnabled > AUDIOSYNC_SEND) useNetworkAudio = true; } @@ -2352,6 +2401,11 @@ class AudioReactive : public Usermod { #endif } +#if defined(_MoonModules_WLED_) && defined(WLEDMM_FASTPATH) + void loop2(void) { + loop(); + } +#endif bool getUMData(um_data_t **data) { @@ -2399,7 +2453,8 @@ class AudioReactive : public Usermod { if (FFT_Task) { vTaskResume(FFT_Task); connected(); // resume UDP - } else + } else { + if (audioSource) // WLEDMM only create FFT task if we have a valid audio source // xTaskCreatePinnedToCore( // xTaskCreate( // no need to "pin" this task to core #0 xTaskCreateUniversal( @@ -2411,9 +2466,10 @@ class AudioReactive : public Usermod { &FFT_Task // Task handle , 0 // Core where the task should run ); + } } micDataReal = 0.0f; // just to be sure - if (enabled) disableSoundProcessing = false; + if (enabled && audioSource) disableSoundProcessing = false; updateIsRunning = init; #if defined(ARDUINO_ARCH_ESP32) && defined(SR_DEBUG) @@ -2568,7 +2624,7 @@ class AudioReactive : public Usermod { } else { // error during audio source setup infoArr.add(F("not initialized")); - infoArr.add(F(" - check pin settings")); + if (dmType < 254) infoArr.add(F(" - check pin settings")); } } @@ -2804,6 +2860,16 @@ class AudioReactive : public Usermod { JsonObject top = root[FPSTR(_name)]; bool configComplete = !top.isNull(); +#ifdef ARDUINO_ARCH_ESP32 + // remember previous values + auto oldEnabled = enabled; + auto oldDMType = dmType; + auto oldI2SsdPin = i2ssdPin; + auto oldI2SwsPin = i2swsPin; + auto oldI2SckPin = i2sckPin; + auto oldI2SmclkPin = mclkPin; +#endif + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); #ifdef ARDUINO_ARCH_ESP32 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) @@ -2856,6 +2922,17 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); configComplete &= getJsonValue(top["sync"][F("check_sequence")], audioSyncSequence); + // WLEDMM notify user when a reboot is necessary + #ifdef ARDUINO_ARCH_ESP32 + if (initDone) { + if ((audioSource != nullptr) && (oldDMType != dmType)) errorFlag = ERR_REBOOT_NEEDED; // changing mic type requires reboot + if ( (audioSource != nullptr) && (enabled==true) + && ((oldI2SsdPin != i2ssdPin) || (oldI2SsdPin != i2ssdPin) || (oldI2SckPin != i2sckPin)) ) errorFlag = ERR_REBOOT_NEEDED; // changing mic pins requires reboot + if ((audioSource != nullptr) && (oldI2SmclkPin != mclkPin)) errorFlag = ERR_REBOOT_NEEDED; // changing MCLK pin requires reboot + if ((oldDMType != dmType) && (oldDMType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing from analog mic requires power cycle + if ((oldDMType != dmType) && (dmType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing to analog mic requires power cycle + } + #endif return configComplete; } @@ -2875,6 +2952,11 @@ class AudioReactive : public Usermod { #endif oappend(SET_F("dd=addDropdown(ux,'digitalmic:type');")); + #if SR_DMTYPE==254 + oappend(SET_F("addOption(dd,'None - network receive only (⎌)',254);")); + #else + oappend(SET_F("addOption(dd,'None - network receive only',254);")); + #endif #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) #if SR_DMTYPE==0 oappend(SET_F("addOption(dd,'Generic Analog (⎌)',0);")); @@ -2929,6 +3011,11 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addOption(dd,'AC101 ☾',8);")); #endif + #if SR_DMTYPE==9 + oappend(SET_F("addOption(dd,'ES8311 ☾ (⎌)',9);")); + #else + oappend(SET_F("addOption(dd,'ES8311 ☾',9);")); + #endif #ifdef SR_SQUELCH oappend(SET_F("addInfo(ux+':config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field #endif diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index d502fcc0..2313001b 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -6,16 +6,7 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - - WLED-MM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied - warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with WLED-MM. If not, see . + @license Licensed under the EUPL-1.2 or later */ @@ -37,6 +28,9 @@ #define SRate_t int #endif +constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0; // I2S port to use (do not change! I2S_NUM_1 possible but this has + // strong limitations -> no MCLK routing, no ADC support, no PDM support + //#include //#include //#include @@ -71,6 +65,11 @@ // data type requested from the I2S driver - currently we always use 32bit //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible +#if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32) + // this is bitter, but necessary to survive + #define I2S_USE_16BIT_SAMPLES +#endif + #ifdef I2S_USE_16BIT_SAMPLES #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT #define I2S_datatype int16_t @@ -207,14 +206,23 @@ class I2SSource : public AudioSource { .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), //.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, #ifdef WLEDMM_FASTPATH + #ifdef WLED_ENABLE_HUB75MATRIX + .intr_alloc_flags = ESP_INTR_FLAG_IRAM|ESP_INTR_FLAG_LEVEL1, // HUB75 seems to get into trouble if we allocate a higher priority interrupt + .dma_buf_count = 18, // 100ms buffer (128 * dma_buf_count / sampleRate) + #else #if CONFIG_IDF_TARGET_ESP32 && !defined(BOARD_HAS_PSRAM) // still need to test on boards with PSRAM .intr_alloc_flags = ESP_INTR_FLAG_IRAM|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3, // IRAM flag reduces missed samples #else .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3, // seems to reduce noise #endif .dma_buf_count = 24, // 140ms buffer (128 * dma_buf_count / sampleRate) + #endif #else + #ifdef WLED_ENABLE_HUB75MATRIX + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // HUB75 seems to get into trouble if we allocate a higher priority interrupt + #else .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, + #endif .dma_buf_count = 8, #endif .dma_buf_len = _blockSize, @@ -291,6 +299,9 @@ class I2SSource : public AudioSource { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (ESP.getChipRevision() == 0) _config.use_apll = false; // APLL is broken on ESP32 revision 0 #endif + #if defined(WLED_ENABLE_HUB75MATRIX) + _config.use_apll = false; // APLL needed for HUB75 DMA driver ? + #endif #endif if (_i2sMaster == false) { @@ -327,7 +338,7 @@ class I2SSource : public AudioSource { //DEBUGSR_PRINTF("[AR] I2S: SD=%d, WS=%d, SCK=%d, MCLK=%d\n", i2ssdPin, i2swsPin, i2sckPin, mclkPin); - esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); + esp_err_t err = i2s_driver_install(AR_I2S_PORT, &_config, 0, nullptr); if (err != ESP_OK) { ERRORSR_PRINTF("AR: Failed to install i2s driver: %d\n", err); return; @@ -345,18 +356,18 @@ class I2SSource : public AudioSource { DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in SLAVE mode.")); } - err = i2s_set_pin(I2S_NUM_0, &_pinConfig); + err = i2s_set_pin(AR_I2S_PORT, &_pinConfig); if (err != ESP_OK) { ERRORSR_PRINTF("AR: Failed to set i2s pin config: %d\n", err); - i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver + i2s_driver_uninstall(AR_I2S_PORT); // uninstall already-installed driver return; } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) - err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed. + err = i2s_set_clk(AR_I2S_PORT, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed. if (err != ESP_OK) { ERRORSR_PRINTF("AR: Failed to configure i2s clocks: %d\n", err); - i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver + i2s_driver_uninstall(AR_I2S_PORT); // uninstall already-installed driver return; } #endif @@ -365,7 +376,7 @@ class I2SSource : public AudioSource { virtual void deinitialize() { _initialized = false; - esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); + esp_err_t err = i2s_driver_uninstall(AR_I2S_PORT); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); return; @@ -388,13 +399,7 @@ class I2SSource : public AudioSource { if (num_samples > I2S_SAMPLES_MAX) num_samples = I2S_SAMPLES_MAX; // protect the buffer from overflow - if (_sampleRate == 96000) { - num_samples *= 4; - err = i2s_read(I2S_NUM_0, (void *)newSamples_buff, num_samples * sizeof(I2S_datatype), &bytes_read, portMAX_DELAY); - } else { - err = i2s_read(I2S_NUM_0, (void *)newSamples, num_samples * sizeof(I2S_datatype), &bytes_read, portMAX_DELAY); - } - + err = i2s_read(AR_I2S_PORT, (void *)newSamples, num_samples * sizeof(I2S_datatype), &bytes_read, portMAX_DELAY); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to get samples: %d\n", err); return; @@ -658,6 +663,106 @@ class ES8388Source : public I2SSource { }; +/* ES8311 Sound Module + This is an I2S sound processing unit that requires initialization over + I2C before I2S data can be received. +*/ +class ES8311Source : public I2SSource { + private: + // I2C initialization functions for es8311 + void _es8311I2cBegin() { + Wire.setClock(100000); + } + + void _es8311I2cWrite(uint8_t reg, uint8_t val) { + #ifndef ES8311_ADDR + #define ES8311_ADDR 0x18 // default address is... foggy + #endif + Wire.beginTransmission(ES8311_ADDR); + Wire.write((uint8_t)reg); + Wire.write((uint8_t)val); + uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK + if (i2cErr != 0) { + DEBUGSR_PRINTF("AR: ES8311 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, ES8311_ADDR, reg, val); + } + } + + void _es8311InitAdc() { + // + // Currently only tested with the ESP32-P4 EV board with the onboard mic. + // Datasheet with I2C commands: https://dl.xkwy2018.com/downloads/RK3588/01_Official%20Release/04_Product%20Line%20Branch_NVR/02_Key%20Device%20Specifications/ES8311%20DS.pdf + // + _es8311I2cBegin(); + _es8311I2cWrite(0x00, 0b00011111); // RESET, default value + _es8311I2cWrite(0x45, 0b00000000); // GP, default value + _es8311I2cWrite(0x01, 0b00111010); // CLOCK MANAGER was 0b00110000 trying 0b00111010 (MCLK enable?) + + _es8311I2cWrite(0x02, 0b00000000); // 22050hz calculated + _es8311I2cWrite(0x05, 0b00000000); // 22050hz calculated + _es8311I2cWrite(0x03, 0b00010000); // 22050hz calculated + _es8311I2cWrite(0x04, 0b00010000); // 22050hz calculated + _es8311I2cWrite(0x07, 0b00000000); // 22050hz calculated + _es8311I2cWrite(0x08, 0b11111111); // 22050hz calculated + _es8311I2cWrite(0x06, 0b11100011); // 22050hz calculated + + _es8311I2cWrite(0x16, 0b00100000); // ADC was 0b00000011 trying 0b00100100 now + _es8311I2cWrite(0x0B, 0b00000000); // SYSTEM at default + _es8311I2cWrite(0x0C, 0b00100000); // SYSTEM was 0b00001111 trying 0b00100000 + _es8311I2cWrite(0x10, 0b00010011); // SYSTEM was 0b00011111 trying 0b00010011 + _es8311I2cWrite(0x11, 0b01111100); // SYSTEM was 0b01111111 trying 0b01111100 + _es8311I2cWrite(0x00, 0b11000000); // *** RESET (again - seems important?) + _es8311I2cWrite(0x01, 0b00111010); // CLOCK MANAGER was 0b00111111 trying 0b00111010 (again??) + _es8311I2cWrite(0x14, 0b00010000); // *** SYSTEM was 0b00011010 trying 0b00010000 (or 0b01111010) (PGA gain) + _es8311I2cWrite(0x12, 0b00000000); // SYSTEM - DAC, likely don't care + _es8311I2cWrite(0x13, 0b00010000); // SYSTEM - output, likely don't cate + _es8311I2cWrite(0x09, 0b00001000); // SDP IN (likely don't care) was 0b00001100 (16-bit) - changed to 0b00001000 (I2S 32-bit) + _es8311I2cWrite(0x0A, 0b00001000); // *** SDP OUT, was 0b00001100 trying 0b00001000 (I2S 32-bit) + _es8311I2cWrite(0x0E, 0b00000010); // *** SYSTEM was 0b00000010 trying 0b00011010 (seems best so far!) (or 0b00000010) + _es8311I2cWrite(0x0F, 0b01000100); // SYSTEM was 0b01000100 + _es8311I2cWrite(0x15, 0b00000000); // ADC soft ramp (disabled) + _es8311I2cWrite(0x1B, 0b00000101); // ADC soft-mute was 0b00000101 + _es8311I2cWrite(0x1C, 0b01100101); // ADC EQ and offset freeze was 0b01100101 (bad at 0b00101100) + _es8311I2cWrite(0x17, 0b10111111); // ADC volume was 0b11111111 trying ADC volume 0b10111111 = 0db (maxgain) 0x16 + _es8311I2cWrite(0x44, 0b00000000); // 0b10000000 - loopback test. on: 0x88; off: 0x00; mic--" speak + + } + + public: + ES8311Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : + I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; + }; + + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("es8311Source:: initialize();"); + + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S es8311 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } + // BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here. + // Workaround: Set I2C pins here, which will also set them globally. + // Bug also exists in ES7243. + if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" + ERRORSR_PRINTF("\nAR: invalid es8311 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins + ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + + // First route mclk, then configure ADC over I2C, then configure I2S + _es8311InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + I2SSource::deinitialize(); + } + +}; + class WM8978Source : public I2SSource { private: // I2C initialization functions for WM8978 @@ -1074,8 +1179,8 @@ class SPH0654 : public I2SSource { I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // these registers are only existing in "classic" ESP32 - REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); - REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); + REG_SET_BIT(I2S_TIMING_REG(AR_I2S_PORT), BIT(9)); + REG_SET_BIT(I2S_CONF_REG(AR_I2S_PORT), I2S_RX_MSB_SHIFT); #else #warning FIX ME! Please. #endif diff --git a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h index 4cc50671..a4c557dd 100644 --- a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h +++ b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h @@ -6,16 +6,7 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - - WLED-MM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied - warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with WLED-MM. If not, see . + @license Licensed under the EUPL-1.2 or later */ diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1fbc74e6..4e6d7023 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3,25 +3,6 @@ WS2812FX.cpp contains all effect methods Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) - Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - Modified heavily for WLED */ @@ -29,6 +10,13 @@ #include "FX.h" #include "fcn_declare.h" +#ifdef WLEDMM_FASTPATH +#undef SEGMENT +#undef SEGENV +#define SEGMENT (*strip._currentSeg) // saves us many calls to strip._segments[strip.getCurrSegmentId()] +#define SEGENV SEGMENT +#endif + #define IBN 5100 // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) @@ -37,10 +25,23 @@ #define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) +#if 0 // for benchmarking - change to "#if 1" to use less accurate, but 30% faster FastLed sin8 and cos8 functions +#define sin8_t sin8 +#define cos8_t cos8 +#define sin16_t sin16 +#define cos16_t cos16 +#define beatsin8_t beatsin8 +#define beatsin88_t beatsin88 +#define beatsin16_t beatsin16 +#endif + +// WLEDMM replace abs8 by abs, as abs8 does not work for numbers >127 +#define abs8(x) abs(x) + // effect utility functions static uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; - return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0 + return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0 } static uint16_t triwave16(uint16_t in) { @@ -74,7 +75,7 @@ static int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { return 0; } -// float version of map() // WLEDMM moved here so its available for all effects +// float version of map() // WLEDMM moved here so it is available for all effects static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ if (in_max == in_min) return (out_min); // WLEDMM avoid div/0 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; @@ -373,7 +374,7 @@ uint16_t mode_breath(void) { counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); - var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 + var = sin16_t(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 } uint8_t lum = 30 + var; @@ -555,7 +556,7 @@ uint16_t running_base(bool saw, bool dual=false) { } a = 255 - a; } - uint8_t s = dual ? sin_gap(a) : sin8(a); + uint8_t s = dual ? sin_gap(a) : sin8_t(a); uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); if (dual) { uint16_t b = (SEGLEN-1-i)*x_scale - counter; @@ -794,7 +795,7 @@ static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!; * Android loading circle */ uint16_t mode_android(void) { - + if (SEGLEN <= 1) return mode_static(); // WLEDMM to prevent division by zero for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } @@ -1042,7 +1043,7 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st */ #define FLASH_COUNT 4 uint16_t mode_chase_flash(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGLEN; i++) { @@ -1752,7 +1753,7 @@ uint16_t mode_tricolor_fade(void) { return FRAMETIME; } -static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; +static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!;01"; /* @@ -1793,6 +1794,61 @@ uint16_t mode_multi_comet(void) { } static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet"; +// audioreactive multi-comet by @softhack007 +uint16_t mode_multi_comet_ar(void) { + constexpr unsigned MAX_COMETS = 16; // was 8 + uint32_t cycleTime = max(1, int((255 - SEGMENT.speed)/4)); + uint32_t it = strip.now / cycleTime; + if (SEGENV.step == it) return FRAMETIME; // too early + + if (!SEGENV.allocateData(sizeof(uint16_t) * MAX_COMETS)) return mode_static(); //allocation failed + uint16_t* comets = reinterpret_cast(SEGENV.data); + if (SEGENV.call == 0) { // do some initializations + SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); + for(uint8_t i=0; i < MAX_COMETS; i++) comets[i] = SEGLEN; // WLEDMM make sure comments are started individually + SEGENV.aux0 = 0; + } + SEGMENT.fade_out(254 - SEGMENT.intensity/2); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) um_data = simulateSound(SEGMENT.soundSim); + float volumeSmth = *(float*) um_data->u_data[0]; + int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; + uint8_t samplePeak = *(uint8_t*) um_data->u_data[3]; + + uint16_t armed = SEGENV.aux0; // allows to delay comet launch + + #if defined(ARDUINO_ARCH_ESP32) + random16_add_entropy(esp_random() & 0xFFFF); // improve randomness (esp32) + #endif + bool shotOne = false; // avoids starting several coments at the same time (invisible due to overlap) + for(unsigned i=0; i < MAX_COMETS; i++) { + if(comets[i] < SEGLEN) { + // draw comet + uint16_t index = comets[i]; + if (SEGCOLOR(2) != 0) + SEGMENT.setPixelColor(index, i % 2 ? SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); + else + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + comets[i]++; // move + } else { + // randomly launch a new comet + if (random16(min(uint16_t(256), SEGLEN)) < 3) armed++; // new comet loaded and ready + if (armed > 2) armed = 2; // max three armed at once (avoid overlap) + // delay comet "launch" during silence, and wait until next beat + if ( (armed > 0) && (shotOne == false) + && (volumeSmth > 1.0f) && ((samplePeak > 0) || (volumeRaw > 104)) ) { // delayed lauch - wait until peak, don't launch in silence + comets[i] = 0; // start a new comet! + armed--; // un-arm one + shotOne = true; + } + } + } + SEGENV.aux0 = armed; + SEGENV.step = it; + return FRAMETIME; +} +static const char _data_FX_MODE_MULTI_COMET_AR[] PROGMEM = "Multi Comet audio ☾@Speed,Tail Length;!,!;!;1v;sx=160,ix=32,m12=7,si=1"; // Pinwheel, WeWillRockU /* * Running random pixels ("Stream 2") @@ -1943,13 +1999,13 @@ uint16_t mode_pride_2015(void) { uint16_t sPseudotime = SEGENV.step; uint16_t sHue16 = SEGENV.aux0; - uint8_t sat8 = beatsin88( 87, 220, 250); - uint8_t brightdepth = beatsin88( 341, 96, 224); - uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); - uint8_t msmultiplier = beatsin88(147, 23, 60); + uint8_t sat8 = beatsin88_t( 87, 220, 250); + uint8_t brightdepth = beatsin88_t( 341, 96, 224); + uint16_t brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256)); + uint8_t msmultiplier = beatsin88_t(147, 23, 60); uint16_t hue16 = sHue16;//gHue * 256; - uint16_t hueinc16 = beatsin88(113, 1, 3000); + uint16_t hueinc16 = beatsin88_t(113, 1, 3000); if(SEGENV.call == 0) { SEGMENT.setUpLeds(); // WLEDMM use lossless getPixelColor() @@ -1957,15 +2013,14 @@ uint16_t mode_pride_2015(void) { } sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88( 400, 5,9); + sHue16 += duration * beatsin88_t( 400, 5,9); uint16_t brightnesstheta16 = sPseudotime; - for (int i = 0 ; i < SEGLEN; i++) { hue16 += hueinc16; uint8_t hue8 = hue16 >> 8; brightnesstheta16 += brightnessthetainc16; - uint16_t b16 = sin16( brightnesstheta16 ) + 32768; + uint16_t b16 = sin16_t( brightnesstheta16 ) + 32768; uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; @@ -2021,7 +2076,7 @@ uint16_t mode_partyjerk() { speed = SEGMENT.speed * map2(SEGMENT.custom2, 0, 255, 0, 100); } else { speed = SEGMENT.speed; - }; + } SEGENV.step += speed; counter = SEGENV.step >> 8; @@ -2036,7 +2091,7 @@ uint16_t mode_partyjerk() { CRGB rgb(CHSV(SEGENV.aux1, 255, activeColor)); SEGMENT.setPixelColor((uint16_t)i, rgb.r, rgb.g, rgb.b); - }; + } return FRAMETIME; } // mode_partyjerk() @@ -2056,7 +2111,7 @@ uint16_t mode_juggle(void) { CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { - uint16_t index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + uint16_t index = 0 + beatsin88_t((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); fastled_col = CRGB(SEGMENT.getPixelColor(index)); fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); SEGMENT.setPixelColor(index, fastled_col); @@ -2128,12 +2183,16 @@ uint16_t mode_fire_2012() { const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + #if defined(ARDUINO_ARCH_ESP32) + random16_add_entropy(esp_random() & 0xFFFF); // improves randonmess + #endif + // Step 1. Cool down every cell a little for (int i = 0; i < SEGLEN; i++) { uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); uint8_t minTemp = (i> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map LED color based on noise data + uint8_t index = sin8_t(noise * 3); // map LED color based on noise data //fastled_col = ColorFromPalette(SEGPALETTE, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); @@ -2291,7 +2350,7 @@ uint16_t mode_noise16_2() { uint16_t shift_x = SEGENV.step >> 6; // x as a function of time uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field uint8_t noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map led color based on noise data + uint8_t index = sin8_t(noise * 3); // map led color based on noise data //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); @@ -2315,7 +2374,7 @@ uint16_t mode_noise16_3() { uint32_t real_y = (i + shift_y) * scale; // based on the precalculated positions uint32_t real_z = SEGENV.step*8; uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map led color based on noise data + uint8_t index = sin8_t(noise * 3); // map led color based on noise data //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); @@ -2396,7 +2455,7 @@ uint16_t mode_colortwinkle() { } } } - return FRAMETIME_FIXED_SLOW; + return FRAMETIME_FIXED; } static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade speed,Spawn speed;;!;;m12=0"; //pixels @@ -2404,14 +2463,14 @@ static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade spe //Calm effect, like a lake at night uint16_t mode_lake() { uint8_t sp = SEGMENT.speed/10; - int wave1 = beatsin8(sp +2, -64,64); - int wave2 = beatsin8(sp +1, -64,64); - uint8_t wave3 = beatsin8(sp +2, 0,80); + int wave1 = beatsin8_t(sp +2, -64,64); + int wave2 = beatsin8_t(sp +1, -64,64); + uint8_t wave3 = beatsin8_t(sp +2, 0,80); //CRGB fastled_col; for (int i = 0; i < SEGLEN; i++) { - int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; + int index = cos8_t((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; //fastled_col = ColorFromPalette(SEGPALETTE, map(index,0,255,0,240), lum, LINEARBLEND); //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); @@ -2578,7 +2637,7 @@ uint16_t ripple_base() propI /= 2; uint16_t cx = rippleorigin >> 8; uint16_t cy = rippleorigin & 0xFF; - uint8_t mag = scale8(sin8((propF>>2)), amp); + uint8_t mag = scale8(sin8_t((propF>>2)), amp); if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif @@ -2598,7 +2657,7 @@ uint16_t ripple_base() } else {//randomly create new wave if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { ripples[i].state = 1; - ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); + ripples[i].pos = SEGMENT.is2D() ? ((random16(SEGENV.virtualWidth())<<8) | (random16(SEGENV.virtualHeight()))) : random16(SEGLEN); ripples[i].color = random8(); //color } } @@ -2653,7 +2712,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) uint16_t ticks = ms / SEGENV.aux0; uint8_t fastcycle8 = ticks; uint16_t slowcycle16 = (ticks >> 8) + salt; - slowcycle16 += sin8(slowcycle16); + slowcycle16 += sin8_t(slowcycle16); slowcycle16 = (slowcycle16 * 2053) + 1384; uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); @@ -2953,7 +3012,7 @@ uint16_t mode_bouncing_balls(void) { if (SEGLEN == 1) return mode_static(); //allocate segment data const uint16_t strips = SEGMENT.nrOfVStrips(); // adapt for 2D - const size_t maxNumBalls = 16; + constexpr size_t maxNumBalls = 16; uint16_t dataSize = sizeof(ball) * maxNumBalls; if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed @@ -3057,7 +3116,8 @@ static uint16_t rolling_balls(void) { float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider bool hasCol2 = SEGCOLOR(2); - if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); + //if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); + if (!SEGMENT.check2) SEGMENT.fade_out(253); // WLEDMM adding a bit of trail for (int i = 0; i < numBalls; i++) { float timeSinceLastUpdate = float((strip.now - balls[i].lastBounceUpdate))/cfac; @@ -3105,7 +3165,7 @@ static uint16_t rolling_balls(void) { if (thisHeight < 0.0f) thisHeight = 0.0f; if (thisHeight > 1.0f) thisHeight = 1.0f; - uint16_t pos = round(thisHeight * (SEGLEN - 1)); + uint16_t pos = roundf(thisHeight * (SEGLEN - 1)); SEGMENT.setPixelColor(pos, color); balls[i].lastBounceUpdate = strip.now; balls[i].height = thisHeight; @@ -3123,7 +3183,7 @@ uint16_t sinelon_base(bool dual, bool rainbow=false) { if (SEGLEN == 1) return mode_static(); if (SEGENV.call == 0) { SEGENV.setUpLeds(); SEGMENT.fill(BLACK); } // WLEDMM use lossless getPixelColor() SEGMENT.fade_out(SEGMENT.intensity); - uint16_t pos = beatsin16(SEGMENT.speed/10,0,SEGLEN-1); + uint16_t pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1); if (SEGENV.call == 0) SEGENV.aux0 = pos; uint32_t color1 = SEGMENT.color_from_palette(pos, true, false, 0); uint32_t color2 = SEGCOLOR(2); @@ -3205,7 +3265,7 @@ static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;Bg,, //each needs 19 bytes -//Spark type is used for popcorn, 1D fireworks, and drip +//Spark type is used for popcorn, 1D fireworks typedef struct Spark { float pos, posX; float vel, velX; @@ -3233,6 +3293,11 @@ static uint16_t mode_popcorn_core(bool useaudio) { Spark* popcorn = reinterpret_cast(SEGENV.data); + if (SEGENV.call == 0) { + SEGMENT.fill(BLACK); // WLEDMM clear LEDs at startup + SEGENV.step = strip.now; // initial time + } + bool hasCol2 = SEGCOLOR(2); if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); @@ -3245,9 +3310,9 @@ static uint16_t mode_popcorn_core(bool useaudio) { } struct virtualStrip { - static void runStrip(uint16_t stripNr, Spark* popcorn, bool useaudio, um_data_t *um_data) { // WLEDMM added useaudio and um_data - float gravity = -0.0001f - (SEGMENT.speed/200000.0f); // m/s/s - gravity *= SEGLEN; + static void runStrip(uint16_t stripNr, Spark* popcorn, bool useaudio, um_data_t *um_data, float deltaTime) { // WLEDMM added useaudio and um_data + float gravity = -0.0001f - (SEGMENT.speed/180000.0f); // m/s/s // WLEDMM original value was "-0.0001f - (SEGMENT.speed/200000.0f)" + gravity *= min(max(1, SEGLEN-1), 255); // WLEDMM speed limit 255 uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; if (numPopcorn == 0) numPopcorn = 1; @@ -3258,8 +3323,8 @@ static uint16_t mode_popcorn_core(bool useaudio) { for(int i = 0; i < numPopcorn; i++) { if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position - popcorn[i].pos += popcorn[i].vel; - popcorn[i].vel += gravity; + popcorn[i].pos += popcorn[i].vel * deltaTime; + popcorn[i].vel += gravity * deltaTime; } else { // if kernel is inactive, randomly pop it bool doPopCorn = false; // WLEDMM allows to inhibit new pops // WLEDMM begin @@ -3278,7 +3343,7 @@ static uint16_t mode_popcorn_core(bool useaudio) { uint16_t peakHeight = 128 + random8(128); //0-255 peakHeight = (peakHeight * (SEGLEN -1)) >> 8; - popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); + popcorn[i].vel = sqrtf(-2.01f * gravity * peakHeight); if (SEGMENT.palette) { @@ -3295,19 +3360,31 @@ static uint16_t mode_popcorn_core(bool useaudio) { if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); uint16_t ledIndex = popcorn[i].pos; if (ledIndex < SEGLEN) SEGMENT.setPixelColor(indexToVStrip(ledIndex, stripNr), col); + // WLEDMM add small trail + for (int n=1; n<4; n++) { + float spdLimit = n; + unsigned fade = 128 - 32*n; + uint32_t trailColor = color_fade(col, fade, true); + if ((popcorn[i].vel < -spdLimit) && (ledIndex+n < SEGLEN)) SEGMENT.setPixelColor(indexToVStrip(ledIndex+n, stripNr), trailColor); + if ((popcorn[i].vel > spdLimit) && (ledIndex >= n)) SEGMENT.setPixelColor(indexToVStrip(ledIndex-n, stripNr), trailColor); + } } } } }; + // WLEDMM calculate time passed + uint32_t millisPassed = min(max(1U, unsigned(strip.now - SEGENV.step)), 200U); // constrain between 1 and 200 + SEGENV.step = strip.now; + float deltaTime = useaudio ? float(millisPassed) / 8.0f : float(millisPassed) / 16.0f; // base speed: 64 FPS (normal) / 120fps (audioreactive) for (int stripNr=0; stripNr> 1), maxSparks); - uint16_t dataSize = sizeof(spark) * numSparks; + unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); + unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3644,8 +3721,9 @@ uint16_t mode_exploding_fireworks(void) * Explosion happens where the flare ended. * Size is proportional to the height. */ - int nSparks = flare->pos + random8(4); - nSparks = constrain(nSparks, 4, numSparks); + unsigned nSparks = flare->pos + random8(4); + nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 + nSparks = std::min(nSparks, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { @@ -3710,6 +3788,17 @@ uint16_t mode_exploding_fireworks(void) static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128"; + +//SparkDrop type is used for drip +typedef struct __attribute__ ((packed)) SparkDrop { + float pos; + float boost; // speed "kick" when dropping + float vel; + uint32_t aux; // aux variable (RGBW color) + uint16_t col; + uint8_t colIndex; +} sparkdrop; + /* * Drip Effect * ported of: https://www.youtube.com/watch?v=sru2fXh4r7k @@ -3719,21 +3808,25 @@ uint16_t mode_drip(void) if (SEGLEN == 1) return mode_static(); //allocate segment data uint16_t strips = SEGMENT.nrOfVStrips(); - const int maxNumDrops = 4; - uint16_t dataSize = sizeof(spark) * maxNumDrops; + constexpr int maxNumDrops = 4; + uint16_t dataSize = sizeof(sparkdrop) * maxNumDrops; if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed - Spark* drops = reinterpret_cast(SEGENV.data); + SparkDrop* drops = reinterpret_cast(SEGENV.data); - if (SEGENV.call == 0) SEGMENT.fill(BLACK); // WLEDMM clear LEDs at startup + if (SEGENV.call == 0) { + SEGMENT.fill(BLACK); // WLEDMM clear LEDs at startup + SEGENV.step = strip.now; // initial time + } if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); struct virtualStrip { - static void runStrip(uint16_t stripNr, Spark* drops) { + static void runStrip(uint16_t stripNr, SparkDrop* drops, float deltaTime) { // WLEDMM added deltaTime uint8_t numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 - float gravity = -0.0005f - (float(SEGMENT.speed)/35000.0f); //increased gravity (50000 to 35000) - gravity *= min(max(1, SEGLEN-1), 255); //WLEDMM speed limit 255 + float theSpeed = (SEGMENT.speed * SEGMENT.speed) / 255.0f; // WLEDMM + float gravity = -0.0002f - theSpeed/42000.0f; //gravity // WLEDMM adjusted + gravity *= min(max(1, SEGLEN-1), 255); // WLEDMM speed limit 255 const int sourcedrop = 12; for (int j=0;j255) drops[j].col=255; - SEGMENT.setPixelColor(indexToVStrip(uint16_t(drops[j].pos), stripNr), color_blend(BLACK,dropColor,drops[j].col)); + int intPos = max(0.0f, roundf(drops[j].pos)); // WLEDMM round it first + SEGMENT.setPixelColor(int(indexToVStrip(intPos, stripNr)), color_blend(BLACK,dropColor,drops[j].col)); - drops[j].col += map(SEGMENT.custom1, 0, 255, 1, 6); // swelling + unsigned swell = map(SEGMENT.custom1, 0, 255, 1, 6); // swelling + drops[j].col += swell; + if (drops[j].boost < 4.0f) drops[j].boost += 0.012f * float(swell); // increase mass when swelling uint32_t fallrate = (drops[j].col * (1 + SEGMENT.custom1 * SEGMENT.custom1)) / 192; // WLEDMM specific if (random16() <= (fallrate / 10)) { // random drop => 1% ... 20% probalibity drops[j].colIndex=2; //fall drops[j].col=255; + drops[j].vel = gravity * 2.0f * drops[j].boost; // WLEDMM initial kick } } if (drops[j].colIndex > 1) { // falling if (drops[j].pos > 0.01f) { // fall until end of segment - drops[j].pos += drops[j].vel; + drops[j].pos += drops[j].vel * deltaTime; if (drops[j].pos < 0) drops[j].pos = 0; - drops[j].vel += gravity; // gravity is negative + drops[j].vel += gravity * deltaTime; // gravity is negative - for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets - int intPos = roundf(drops[j].pos) +i; // WLEDMM round it first + int maxLen = 8 + SEGMENT.speed/64; + for (int i=1; i < maxLen-drops[j].colIndex; i++) { // some minor math so we don't expand bouncing droplets + int intPos = roundf(drops[j].pos + float(i)); // WLEDMM round it first + if ((intPos >= SEGLEN) || (intPos < 0)) break; // WLEDMM skip off-screen pixels uint16_t pos = constrain(intPos, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally // WLEDMM bad cast to uint16_t removed SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color_blend(BLACK,dropColor,drops[j].col/i)); //spread pixel with fade while falling } if (drops[j].colIndex > 2) { // during bounce, some water is on the floor - SEGMENT.setPixelColor(indexToVStrip(0, stripNr), color_blend(dropColor,BLACK, (2 * drops[j].col)/3)); // WLEDMM reduced brightness + SEGMENT.addPixelColor(indexToVStrip(0, stripNr), color_blend(dropColor,BLACK, drops[j].col*4)); // WLEDMM darker } } else { // we hit bottom if (drops[j].colIndex > 2) { // already hit once, so back to forming @@ -3782,8 +3882,11 @@ uint16_t mode_drip(void) } else { if (drops[j].colIndex==2) { // init bounce - drops[j].vel = -drops[j].vel/4;// reverse velocity with damping - drops[j].pos += drops[j].vel; + // reverse velocity with damping + if (SEGLEN > 16) drops[j].vel = -drops[j].vel/3.5f; + else drops[j].vel = -drops[j].vel/4.5f; + // do bounce + drops[j].pos += drops[j].vel * deltaTime * drops[j].boost*0.5f; } drops[j].col = sourcedrop*2; drops[j].colIndex = 5; // bouncing @@ -3794,12 +3897,16 @@ uint16_t mode_drip(void) } }; + // WLEDMM calculate time passed + uint32_t millisPassed = min(max(1U, unsigned(strip.now - SEGENV.step)), 180U); // constrain between 1 and 180 + SEGENV.step = strip.now; + float deltaTime = float(millisPassed) / 20.0f; // base speed 50 FPS for (int stripNr=0; stripNr> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. - + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. - uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); + + cos8_t((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. + uint8_t thisBright = qsub8(colorIndex, beatsin8_t(7,0, (128 - (SEGMENT.intensity>>1)))); //CRGB color = ColorFromPalette(SEGPALETTE, colorIndex, thisBright, LINEARBLEND); //SEGMENT.setPixelColor(i, color.red, color.green, color.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0, thisBright)); @@ -3927,8 +4034,8 @@ uint16_t mode_percent(void) { uint8_t percent = SEGMENT.intensity; percent = constrain(percent, 0, 200); - uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 - : SEGLEN * (200 - percent) / 100.0; + uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0f + : SEGLEN * (200 - percent) / 100.0f; uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; @@ -4030,17 +4137,17 @@ static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!;!;01;m1 // Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino // // Add one layer of waves into the led array -CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) +CRGB pacifica_one_layer(uint16_t i, const CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) { uint16_t ci = cistart; uint16_t waveangle = ioff; uint16_t wavescale_half = (wavescale >> 1) + 20; waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i - uint16_t s16 = sin16(waveangle) + 32768; + uint16_t s16 = sin16_t(waveangle) + 32768; uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; ci += (cs * i); - uint16_t sindex16 = sin16(ci) + 32768; + uint16_t sindex16 = sin16_t(ci) + 32768; uint8_t sindex8 = scale16(sindex16, 240); return ColorFromPalette(p, sindex8, bri, LINEARBLEND); } @@ -4072,34 +4179,34 @@ uint16_t mode_pacifica() uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); strip.now = deltat; - uint16_t speedfactor1 = beatsin16(3, 179, 269); - uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint16_t speedfactor1 = beatsin16_t(3, 179, 269); + uint16_t speedfactor2 = beatsin16_t(4, 179, 269); uint32_t deltams1 = (deltams * speedfactor1) / 256; uint32_t deltams2 = (deltams * speedfactor2) / 256; uint32_t deltams21 = (deltams1 + deltams2) / 2; - sCIStart1 += (deltams1 * beatsin88(1011,10,13)); - sCIStart2 -= (deltams21 * beatsin88(777,8,11)); - sCIStart3 -= (deltams1 * beatsin88(501,5,7)); - sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + sCIStart1 += (deltams1 * beatsin88_t(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88_t(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88_t(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88_t(257,4,6)); SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; // Clear out the LED array to a dim background blue-green //SEGMENT.fill(132618); - uint8_t basethreshold = beatsin8( 9, 55, 65); + uint8_t basethreshold = beatsin8_t( 9, 55, 65); uint8_t wave = beat8( 7 ); for (int i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); // Render each of four layers, with different scales and speeds, that vary over time - c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); - c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8(9, 10,38) , 0-beat16(503)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); + c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16_t(3, 11 * 256, 14 * 256), beatsin8_t(10, 70, 130), 0-beat16(301)); + c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16_t(4, 6 * 256, 9 * 256), beatsin8_t(17, 40, 80), beat16(401)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8_t(9, 10,38) , 0-beat16(503)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8_t(8, 10,28) , beat16(601)); // Add extra 'white' to areas where the four layers of light have lined up brightly - uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; + uint8_t threshold = scale8( sin8_t( wave), 20) + basethreshold; wave += 7; uint8_t l = c.getAverageLight(); if (l > threshold) { @@ -4187,7 +4294,7 @@ uint16_t phased_base(uint8_t moder) { // We're making sine wave uint8_t modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5). uint8_t index = strip.now/64; // Set color rotation speed - *phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) + *phase += SEGMENT.speed/32.0f; // You can change the speed of the wave. AKA SPEED (was .4) for (int i = 0; i < SEGLEN; i++) { if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. @@ -4223,7 +4330,7 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine for (int i = 0; i < SEGLEN; i++) { uint8_t ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. - uint8_t pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); + uint8_t pixBri = sin8_t(ranstart + 16 * strip.now/(256-SEGMENT.speed)); if (random8() > SEGMENT.intensity) pixBri = 0; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } @@ -4266,7 +4373,7 @@ uint16_t mode_noisepal(void) { // Slow noise SEGMENT.setPixelColor(i, color.red, color.green, color.blue); } - SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. + SEGENV.aux0 += beatsin8_t(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. return FRAMETIME; } @@ -4339,20 +4446,20 @@ static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //ver */ uint16_t mode_chunchun(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() - SEGMENT.fade_out(254); // add a bit of trail - uint16_t counter = strip.now * (6 + (SEGMENT.speed >> 4)); - uint16_t numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment - uint16_t span = (SEGMENT.intensity << 8) / numBirds; + SEGMENT.fade_out(253); // add a bit of trail // WLEDMM fade rate above 253 has no effect + uint32_t counter = ((strip.now * (96 + SEGMENT.speed)) >> 4); // WLEDMM same result, better resolution + uint16_t numBirds = min(32, 2 + (SEGLEN >> 3)); // 2 + 1/8 of a segment - WLEDMM max 32 + uint32_t span = (SEGMENT.intensity << 8) / numBirds; - for (int i = 0; i < numBirds; i++) + for (unsigned i = 0; i < numBirds; i++) { counter -= span; - uint16_t megumin = sin16(counter) + 0x8000; + uint16_t megumin = sin16_t(counter) + 0x8000; uint16_t bird = uint32_t(megumin * SEGLEN) >> 16; uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping - bird = constrain(bird, 0, SEGLEN-1); + bird = min(bird, uint16_t(SEGLEN-1)); // WLEDMM unsigned is always >= 0 SEGMENT.setPixelColor(bird, c); } return FRAMETIME; @@ -4411,7 +4518,7 @@ uint16_t mode_dancing_shadows(void) if (!initialize) { // advance the position of the spotlight int16_t delta = (float)(time - spotlights[i].lastUpdateTime) * - (spotlights[i].speed * ((1.0 + SEGMENT.speed)/100.0)); + (spotlights[i].speed * ((1.0f + SEGMENT.speed)/100.0f)); if (abs(delta) >= 1) { spotlights[i].position += delta; @@ -4426,15 +4533,15 @@ uint16_t mode_dancing_shadows(void) spotlights[i].colorIdx = random8(); spotlights[i].width = random8(1, 10); - spotlights[i].speed = 1.0/random8(4, 50); + spotlights[i].speed = 1.0f/random8(4, 50); if (initialize) { spotlights[i].position = random16(SEGLEN); - spotlights[i].speed *= random8(2) ? 1.0 : -1.0; + spotlights[i].speed *= random8(2) > 0 ? 1.0 : -1.0; } else { if (random8(2)) { spotlights[i].position = SEGLEN + spotlights[i].width; - spotlights[i].speed *= -1.0; + spotlights[i].speed *= -1.0f; }else { spotlights[i].position = -spotlights[i].width; } @@ -4519,7 +4626,7 @@ uint16_t mode_washing_machine(void) { SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); for (int i = 0; i < SEGLEN; i++) { - uint8_t col = sin8(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); + 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)); } @@ -4548,7 +4655,7 @@ uint16_t mode_blends(void) { uint16_t offset = 0; for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, pixels[offset++]); - if (offset > pixelLen) offset = 0; + if (offset >= pixelLen) offset = 0; } return FRAMETIME; @@ -4682,7 +4789,7 @@ uint16_t mode_tv_simulator(void) { return FRAMETIME; } -static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;"; +static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01"; /* @@ -4725,7 +4832,7 @@ class AuroraWave { alive = true; } - CRGB getColorForLED(int ledIndex) { + CRGB getColorForLED(int ledIndex) const { if(ledIndex < center - width || ledIndex > center + width) return 0; //Position out of range of this wave CRGB rgb; @@ -4780,7 +4887,7 @@ class AuroraWave { } }; - bool stillAlive() { + bool stillAlive() const { return alive; }; }; @@ -4872,12 +4979,12 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel ///////////////////////// // Waveins // ///////////////////////// -// Uses beatsin8() + phase shifting. By: Andrew Tuline +// Uses beatsin8_t() + phase shifting. By: Andrew Tuline uint16_t mode_wavesins(void) { for (int i = 0; i < SEGLEN; i++) { - uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); - uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); + uint8_t bri = sin8_t(strip.now/4 + i * SEGMENT.intensity); + uint8_t index = beatsin8_t(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); } @@ -4899,9 +5006,9 @@ uint16_t mode_FlowStripe(void) { for (int i = 0; i < SEGLEN; i++) { int c = (abs(i - hl) / hl) * 127; - c = sin8(c); - c = sin8(c / 2 + t); - byte b = sin8(c + t/8); + c = sin8_t(c); + c = sin8_t(c / 2 + t); + byte b = sin8_t(c + t/8); SEGMENT.setPixelColor(i, CHSV(b + hue, 255, 255)); } @@ -4931,18 +5038,18 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma } SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails - const unsigned long ratio = 128; // rotation speed + constexpr unsigned long ratio = 128; // rotation speed unsigned long t = strip.now; // timebase // outer stars for (unsigned i = 0; i < 8; i++) { - x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); - y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); + x = beatsin8_t(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); + y = beatsin8_t(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); } // inner stars for (size_t i = 0; i < 4; i++) { - x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); - y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); + x = beatsin8_t(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); + y = beatsin8_t(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); } // central white dot @@ -4978,10 +5085,10 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so SEGENV.aux0++; // hue SEGMENT.fadeToBlackBy(40); for (size_t i = 0; i < numLines; i++) { - byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); - byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (cols - 1)); - byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 24); - byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); + byte x1 = beatsin8_t(2 + SEGMENT.speed/16, 0, (cols - 1)); + byte x2 = beatsin8_t(1 + SEGMENT.speed/16, 0, (rows - 1)); + byte y1 = beatsin8_t(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); + byte y2 = beatsin8_t(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); byte xsteps = abs8(x1 - y1) + 1; @@ -4994,7 +5101,7 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so byte dy = lerp8by8(x2, y2, rate); //SEGMENT.setPixelColorXY(dx, dy, grad ? color.nscale8_video(255-rate) : color); // use addPixelColorXY for different look SEGMENT.addPixelColorXY(dx, dy, color); // use setPixelColorXY for different look - if (grad) SEGMENT.fadePixelColorXY(dx, dy, rate); + if (grad) SEGMENT.fadePixelColorXY(dx, dy, gamma8(rate)); } if (dot) { //add white point at the ends of line @@ -5012,11 +5119,13 @@ 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. +uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. Phases added by @ewoudwijma if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); + unsigned phases = SEGMENT.custom1; + if (phases > 179) phases = 179 + 2.5f * (phases - 179); // boost for values > 179 if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -5025,15 +5134,31 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.fadeToBlackBy(64); + // WLEDMM optimized to prevent holes at height > 32 + int lastY1 = -1; + int lastY2 = -1; for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + // 256 is a complete phase; half a phase of dna is 128 + // unsigned phase = i * 4 * phases / cols; // original formula; cols ==0 cannot happen due to the for loop + unsigned phase = i * 4 * phases / 128; // WLEDMM this reproduces the previous behaviour at phases=127 + int posY1 = beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, phase ); + int posY2 = beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, phase+128); + + if ((i==0) || ((abs(lastY1 - posY1) < 2) && (abs(lastY2 - posY2) < 2))) { // use original code when no holes + SEGMENT.setPixelColorXY(i, posY1, ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, posY2, ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10+128), LINEARBLEND)); + } else { // draw line to prevent holes + SEGMENT.drawLine(i-1, lastY1, i, posY1, ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.drawLine(i-1, lastY2, i, posY2, ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10+128), LINEARBLEND)); + } + lastY1 = posY1; + lastY2 = posY2; } SEGMENT.blur(SEGMENT.intensity>>3); return FRAMETIME; } // mode_2Ddna() -static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; +static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur,Phases;;!;2"; ///////////////////////// @@ -5057,17 +5182,19 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { - uint16_t x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); - uint16_t x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); + uint16_t x = beatsin8_t(speeds, 0, cols - 1, 0, i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, i * freq + 128); + uint16_t x1 = beatsin8_t(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); uint8_t hue = (i * 128 / rows) + ms; // skip every 4th row every now and then (fade it more) if ((i + ms / 8) & 3) { // draw a gradient line between x and x1 x = x / 2; x1 = x1 / 2; - uint8_t steps = abs8(x - x1) + 1; + unsigned steps = abs8(x - x1) + 1; + bool positive = (x1 >= x); // direction of drawing for (size_t k = 1; k <= steps; k++) { - uint8_t rate = k * 255 / steps; - uint8_t dx = lerp8by8(x, x1, rate); + unsigned rate = k * 255 / steps; + //unsigned dx = lerp8by8(x, x1, rate); + unsigned dx = positive? (x + k-1) : (x - k+1); // behaves the same as "lerp8by8" but does not create holes //SEGMENT.setPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND).nscale8_video(rate)); SEGMENT.addPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND)); // use setPixelColorXY for different look SEGMENT.fadePixelColorXY(dx, i, rate); @@ -5086,6 +5213,7 @@ 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 + // optimized for large panels by @softhack007 if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); @@ -5096,22 +5224,44 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli SEGMENT.fill(BLACK); } - SEGMENT.fadeToBlackBy(128); + if (SEGMENT.intensity > 1) SEGMENT.fadeToBlackBy(128); + else SEGMENT.fill(BLACK); // WLEDMM fill is faster than fade + const float maxDim = max(cols, rows)/2.0f; - const uint16_t maxDim = MAX(cols, rows)/2; - unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); - unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup - for (float i = 1; i < maxDim; i += 0.25) { - float angle = radians(t * (maxDim - i)); - uint16_t myX = (cols>>1) + (uint16_t)(sinf(angle) * i) + (cols%2); - uint16_t myY = (rows>>1) + (uint16_t)(cosf(angle) * i) + (rows%2); - SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); + // WLEDMM calculate timebase in float, so we don't need to worry about rounding + const float strip_now = strip.now & 0x003FFFFF; // float can exactly represent numbers up to 22bit + float t; + if (maxDim < 6.0f) t = strip_now / float(16U - (SEGMENT.speed>>4)); // up to 12 (faster) + else if (maxDim <= 16.0f) t = strip_now / float(32U - (SEGMENT.speed>>3)); // 12..32 (standard) + else if (maxDim <= 32.0f) t = strip_now / float(64U - (SEGMENT.speed>>2)); // 32..64 (slower) + else t = strip_now / float(256U - SEGMENT.speed); // above 64 (slowest) + + // WLEDMM pre-calculate some values to speed up the main loop + const int colsCenter = (cols >> 1) + (cols % 2); + const int rowsCenter = (rows >> 1) + (rows % 2); + unsigned t_20 = t/20.0f; // softhack007: pre-calculating this gives about 10% speedup + const float step = (maxDim < 6.0f) ? 0.52f : (maxDim > 24.0f) ? 0.16666666f : 0.25f; // WLEDMM more detail on larger panels + + for (float i = 1.0f; i <= maxDim; i += step) { + unsigned i_20 = i * 20.0f; + float t_maxdim = t * (maxDim - i); + float angle = float(DEG_TO_RAD) * t_maxdim; + // WLEDMM reduce angle to [0 ... 2*PI] - several times faster than letting sinf() do the job + float baseAngle = max(0.0f, floorf(angle * float(1.0 / M_TWOPI)) * float(M_TWOPI)); // multiple of 2_PI (360 deg) that's included in angle + angle -= baseAngle; + + int mySin = sinf(angle) * i; + int myCos = cosf(angle) * i; + + if ((unsigned(colsCenter+mySin) < cols) && (unsigned(rowsCenter+myCos) < rows)) // don't draw invisible pixels + SEGMENT.setPixelColorXY(colsCenter+mySin, rowsCenter+myCos, ColorFromPalette(SEGPALETTE, i_20 + t_20, 255, LINEARBLEND)); + if ((SEGMENT.check1) && (unsigned(colsCenter+myCos) < cols) && (unsigned(rowsCenter+mySin) < rows)) + SEGMENT.setPixelColorXY(colsCenter+myCos, rowsCenter+mySin, ColorFromPalette(SEGPALETTE, i_20 + t_20, 255, LINEARBLEND)); // twin mode } - SEGMENT.blur(SEGMENT.intensity>>3); - + SEGMENT.blur(SEGMENT.intensity>>((!SEGMENT.check2) * 3), SEGMENT.check2); // user-defined blur - thanks @dedehai return FRAMETIME; } // mode_2DDrift() -static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2"; +static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur,,,,Twin,Smear;;!;2;ix=0"; ////////////////////////// @@ -5165,9 +5315,9 @@ uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.so SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { - SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), - beatsin8(SEGMENT.intensity/8 - i, 0, rows - 1), - ColorFromPalette(SEGPALETTE, beatsin8(12, 0, 255), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(beatsin8_t(SEGMENT.speed/8 + i, 0, cols - 1), + beatsin8_t(SEGMENT.intensity/8 - i, 0, rows - 1), + ColorFromPalette(SEGPALETTE, beatsin8_t(12, 0, 255), 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom1>>3); @@ -5177,8 +5327,278 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f /////////////////////////////////////////// -// 2D Cellular Automata Game of life // +// 2D Cellular Automata Game of Life // /////////////////////////////////////////// +typedef struct Cell { + uint8_t alive : 1, neighbors : 3, toggleStatus : 1, superDead : 1, oscillatorCheck : 1, spaceshipCheck : 1; +} Cell; + +class GameOfLifeGrid { + private: + Cell* cells; + const int cols, rows, maxIndex; + const int nOffsets[8] = {-cols-1, -cols, -cols+1, -1, 1, cols-1, cols, cols+1}; // Neighbor offsets + const int8_t offsetX[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + const int8_t offsetY[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + public: + GameOfLifeGrid(Cell* data, int c, int r) : cells(data), cols(c), rows(r), maxIndex(r * c) {} + void getNeighborIndexes(unsigned neighbors[9], unsigned cIndex, unsigned x, unsigned y, bool wrap) const { + unsigned neighborCount = 0; + bool edgeCell = x == 0 || x == cols-1 || y == 0 || y == rows-1; + for (unsigned i = 0; i < 8; ++i) { + unsigned nIndex = cIndex + nOffsets[i]; + if (edgeCell) { + int nX = x + offsetX[i], nY = y + offsetY[i]; + if (wrap) { + if (nX < 0) nIndex += cols; + else if (nX >= cols) nIndex -= cols; + if (nY < 0) nIndex += maxIndex; + else if (nY >= rows) nIndex -= maxIndex; + } + else { // Wrap disabled, skip out of bound neighbors + if (nX < 0 || nX >= cols || nY < 0 || nY >= rows) continue; + } + } + neighbors[++neighborCount] = nIndex; + } + neighbors[0] = neighborCount; + } + void setCell(unsigned cIndex, unsigned x, unsigned y, bool alive, bool wrap) const { + Cell* cell = &cells[cIndex]; + if (alive == cell->alive) return; // No change + cell->alive = alive; + unsigned neighbors[9]; + getNeighborIndexes(neighbors, cIndex, x, y, wrap); + int val = alive ? 1 : -1; + for (unsigned i = 1; i <= neighbors[0]; ++i) cells[neighbors[i]].neighbors += val; + } + void recalculateEdgeNeighbors(bool wrap) const { + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell* cell = &cells[cIndex]; + if (x == 0 || x == cols - 1 || y == 0 || y == rows - 1) { + cell->neighbors = 0; + cell->superDead = 0; + + unsigned neighbors[9]; + getNeighborIndexes(neighbors, cIndex, x, y, wrap); + + for (unsigned i = 1; i <= neighbors[0]; ++i) { + if (cells[neighbors[i]].alive) ++cell->neighbors; + } + } + } + } +}; + +uint16_t 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) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + const size_t dataSize = SEGMENT.length() * sizeof(Cell); // Cell = 2 bytes + const size_t totalSize = dataSize + 6; // 6 bytes for prevRows(2), prevCols(2), prevPalette, prevWrap + + if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed + uint16_t *prevRows = reinterpret_cast(SEGENV.data); + uint16_t *prevCols = reinterpret_cast(SEGENV.data + 2); + uint8_t *prevPalette = reinterpret_cast (SEGENV.data + 4); + bool *prevWrap = reinterpret_cast (SEGENV.data + 5); + Cell *cells = reinterpret_cast (SEGENV.data + 6); + + uint16_t& generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability + uint16_t& gliderLength = SEGENV.aux1; + bool allColors = SEGMENT.check1; + bool overlayBG = SEGMENT.check2; + bool wrap = SEGMENT.check3; + bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode + byte blur = overlayBG ? 255 : bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 220, 255, 10); + uint32_t bgColor = SEGCOLOR(1); + + GameOfLifeGrid grid(cells, cols, rows); + + // If rows or cols change due to mirror/transpose, neighbor counts need to be recalculated. Just reset the game. + bool setup = SEGENV.call == 0 || rows != *prevRows || cols != *prevCols; + + if (setup) { + SEGMENT.setUpLeds(); + SEGMENT.fill(bgColor); // to make sure that segment buffer and physical leds are aligned initially + SEGENV.step = 0; + *prevRows = rows; + *prevCols = cols; + + // Calculate glider length LCM(rows,cols)*4 once + uint8_t a = rows; + uint8_t b = cols; + while (b) { + uint8_t t = b; + b = a % b; + a = t; + } + gliderLength = cols * rows / a * 4; + } + + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix + bool paused = SEGENV.step > strip.now; + + // Setup New Game of Life + if ((!paused && generation == 0) || setup) { + SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds + paused = true; + generation = 1; + *prevWrap = wrap; + *prevPalette = SEGMENT.palette; + + //Setup Grid + memset(cells, 0, dataSize); + random16_set_seed(strip.now>>2); //seed the random generator + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) { + #if defined(ARDUINO_ARCH_ESP32) + random16_add_entropy(esp_random() & 0xFFFF); + #endif + for (unsigned x = 0; x < cols; ++x, ++cIndex) { + if ((random16() & 0xFF) < 82) { // ~32% + grid.setCell(cIndex, x, y, true, wrap); + cells[cIndex].toggleStatus = 1; // Used to set initial color + } + else cells[cIndex].superDead = 1; + } + } + } + + bool palChanged = SEGMENT.palette != *prevPalette && !allColors; + if (palChanged) *prevPalette = SEGMENT.palette; + + // Enter redraw loop if not updating or palette changed. + if (palChanged || paused || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,1,60))) { //(1 - 60) updates/sec 255 is uncapped + // Redraw if paused (remove blur), palette changed, overlaying background if not max speed (avoid flicker) + // Generation 1 draws alive cells randomly and fades dead cells + bool newGame = generation == 1; + if (paused || palChanged || overlayBG) { + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell& cell = cells[cIndex]; + if (!newGame && cell.superDead) continue; // Skip super dead cells unless new game + bool needsColor = (newGame && cell.toggleStatus); + if (cell.alive) { + if ((needsColor && !random(10)) || palChanged) { + cell.toggleStatus = 0; + uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); + SEGMENT.setPixelColorXY(x,y, randomColor); // Palette changed or needs initial color + } + else if (overlayBG && !needsColor) SEGMENT.setPixelColorXY(x,y, SEGMENT.getPixelColorXY(x,y)); // Redraw alive cells for overlayBG + } // Dead + else if (paused && !overlayBG) { + uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); + uint32_t blended = color_blend(cellColor, bgColor, bgBlendMode ? 16 : blur); + if (blended == cellColor) blended = bgColor; // color_blend fix + if ((bgBlendMode && newGame) || !bgBlendMode) SEGMENT.setPixelColorXY(x, y, blended); // Blur dead cells when paused + } + } + } + return FRAMETIME; + } + + // Repeat detection + unsigned aliveCount = 0; // Detects empty grids and solo gliders (for smaller grids) + bool updateOscillator = generation % 16 == 0; + bool updateSpaceship = gliderLength && generation % gliderLength == 0; + bool repeatingOscillator = true, repeatingSpaceship = true; + + // First Loop: Applies rules, sets toggleStatus, detects repeating patterns + // Does not update cells yet to prevent neighbor counts from changing mid-loop + int maxIndex = rows * cols; + for (unsigned i = 0; i < maxIndex; ++i) { + Cell& cell = cells[i]; + + if (cell.alive) ++aliveCount; + if (repeatingOscillator && cell.oscillatorCheck != cell.alive) repeatingOscillator = false; + if (repeatingSpaceship && cell.spaceshipCheck != cell.alive) repeatingSpaceship = false; + if (updateOscillator) cell.oscillatorCheck = cell.alive; + if (updateSpaceship) cell.spaceshipCheck = cell.alive; + + if (cell.alive && (cell.neighbors < 2 || cell.neighbors > 3)) cell.toggleStatus = 1; // Loneliness or Overpopulation + else if (!cell.alive && cell.neighbors == 3) { cell.toggleStatus = 1; cell.superDead = 0; } // Reproduction + else cell.toggleStatus = 0; // No change + } + + uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); // Backup color + if (generation <= 8 && !bgBlendMode) blur = 255 - (((generation-1) * (255 - blur)) >> 3); // Ramp up blur for first 8 generations + + bool disableWrap = !wrap || generation % 1500 == 0 || aliveCount == 5; // Disable wrap every 1500 generations to prevent undetected repeats + if (*prevWrap != !disableWrap) { grid.recalculateEdgeNeighbors(!disableWrap); *prevWrap = !disableWrap; } + + // Second Loop: Updates cells, sets colors, and detects super dead cells + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell& cell = cells[cIndex]; + if (cell.superDead) continue; // Skip super dead cells (bgColor dead cells) + + uint32_t cellColor = SEGMENT.getPixelColorXY(x, y); + + if (cell.toggleStatus) { + if (cell.alive) { // Dies + grid.setCell(cIndex, x, y, false, !disableWrap); + if (cellColor != bgColor) color = cellColor; + if (!overlayBG) SEGMENT.setPixelColorXY(x,y, blur == 255 ? bgColor : color_blend(cellColor, bgColor, blur)); + if (blur == 255 || bgBlendMode) cell.superDead = 1; + } + else { // Reproduction + grid.setCell(cIndex, x, y, true, !disableWrap); + uint32_t birthColor = color; + if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); + else { + // Get Colors + uint32_t nColors[8]; + unsigned colorCount = 0; + unsigned neighbors[9]; + grid.getNeighborIndexes(neighbors, cIndex, x, y, !disableWrap); + + for (unsigned i = 1; i <= neighbors[0]; ++i) { + unsigned nIndex = neighbors[i]; + if (cells[nIndex].alive) { + uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols); + if (nColor == bgColor) continue; + nColors[colorCount++] = nColor; + } + } + if (colorCount) { birthColor = nColors[random8(colorCount)]; color = birthColor; } + } + SEGMENT.setPixelColorXY(x,y, birthColor); + } + } + else { // No change in status + if (cell.alive) { + if (cellColor == bgColor) cellColor = color; else color = cellColor; + SEGMENT.setPixelColorXY(x, y, cellColor); // Redraw alive cells + } + else { // Blur dead + if (blur != 255 && !overlayBG && !bgBlendMode) { + uint32_t blended = color_blend(cellColor, bgColor, blur); // color_blend doesn't always converge to bgColor (this fix needed for fast fps with custom bgColor) + if (blended == cellColor) { blended = bgColor; cell.superDead = 1; } + SEGMENT.setPixelColorXY(x, y, blended); + } + } + } + } + + if (repeatingOscillator || repeatingSpaceship || !aliveCount) { + generation = 0; // reset on next call + SEGENV.step += 1000; // pause final generation for 1 second + return FRAMETIME; + } + + ++generation; + SEGENV.step = strip.now; + return FRAMETIME; +} // mode_2Dgameoflife() +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1"; + +///////////////////////// +// 2D SnowFall // +///////////////////////// static bool getBitValue(const uint8_t* byteArray, size_t n) { size_t byteIndex = n / 8; size_t bitIndex = n % 8; @@ -5193,188 +5613,115 @@ static void setBitValue(uint8_t* byteArray, size_t n, bool value) { else byteArray[byteIndex] &= ~(1 << bitIndex); } - -uint16_t 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) return mode_static(); // not a 2D set-up - +uint16_t mode_2DSnowFall(void) { // By: Brandon Butler + // Uses bit array to track snow/particles + if (!strip.isMatrix) return mode_static(); // Not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte - const size_t detectionSize = sizeof(uint16_t) * 3 + 1; // 2 CRCs, gliderLength, soloGlider boolean - const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); // detectionSize + prevPalette - - if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed - byte *cells = reinterpret_cast(SEGENV.data); - byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); - uint16_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize * 2); - uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t)); - uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 2); - bool *soloGlider = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 3); - uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize * 2 + detectionSize); - - uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability - bool allColors = SEGMENT.check1; - bool overlayBG = SEGMENT.check2; - bool wrap = SEGMENT.check3; - bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode - byte blur = bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 255, 255, 0); + const size_t dataSize = (SEGMENT.length() + 7) / 8; // Round up to nearest byte + + if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed + byte *grid = reinterpret_cast(SEGENV.data); + + bool overlay = SEGMENT.check2; // Overlay is inverted. Only draws non-snow. Layer 1 controls snow color uint32_t bgColor = SEGCOLOR(1); - uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); if (SEGENV.call == 0) { SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially - } - // Setup New Game of Life - if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) { - SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds - generation = 1; - *prevPalette = SEGMENT.palette; - random16_set_seed(strip.now>>2); //seed the random generator - //Setup Grid - memset(cells, 0, dataSize); - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - if (random8(100) < 32) { // ~32% chance of being alive - setBitValue(cells, y * cols + x, true); - if (overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); - else SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop - } - } - memcpy(futureCells, cells, dataSize); - - //Set CRCs - uint16_t crc = crc16((const unsigned char*)cells, dataSize); - *oscillatorCRC = crc; - *spaceshipCRC = crc; - - //Calculate glider length LCM(rows,cols)*4 - uint8_t a = rows; - uint8_t b = cols; - while (b) { - uint8_t t = b; - b = a % b; - a = t; - } - *gliderLength = cols * rows / a * 4; + SEGMENT.fill(bgColor); + SEGENV.aux0 = 0; // Overflow value + memset(grid, 0, dataSize); } - bool blurDead = SEGENV.step > strip.now && blur !=255 && !bgBlendMode && !overlayBG; - bool palChanged = SEGMENT.palette != *prevPalette && !allColors; - bool newGame = generation == 1; - if (palChanged) *prevPalette = SEGMENT.palette; - - // Redraw Loop - // Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker) - // Generation 1 draws alive cells randomly and fades dead cells - if (blurDead || newGame || palChanged || overlayBG) { - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - unsigned cIndex = y * cols + x; - uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); - bool alive = getBitValue(cells, cIndex); - bool aliveBgColor = (!overlayBG && alive && newGame && cellColor == bgColor ); - - if ( alive && (palChanged || (aliveBgColor && !random(12)))) { // Palette change or spawn initial colors randomly - uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y, randomColor); // Recolor alive cells - } - else if ( alive && overlayBG && !aliveBgColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG - if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette - else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));// Blur dead cells (paused) - else if (!alive && !overlayBG && generation == 1) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells on generation 1 + // Draw non snow for inverted overlay + if (overlay) { + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + if (!getBitValue(grid, y * cols + x)) SEGMENT.setPixelColorXY(x, y, bgColor); } } - - if (!SEGMENT.speed || SEGENV.step > strip.now || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,0,60))) return FRAMETIME; //(0 - 60) updates/sec 255 is uncapped - - //Update Game of Life - unsigned aliveCount = 0; // Detects dead grids and solo gliders - bool disableWrap = !wrap || (generation % 1500 == 0 || *soloGlider); // Disable wrap every 1500 generations to prevent undetected repeats - //Loop through all cells. Count neighbors, apply rules, setPixel - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - unsigned cIndex = y * cols + x; - bool cellValue = getBitValue(cells, cIndex); - uint32_t cellColor = SEGMENT.getPixelColorXY(x, y); - if (cellValue) aliveCount++; + + // fix SEGENV.step in case that timebase jumps + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; - unsigned neighbors = 0, colorCount = 0; - unsigned neighborIndexes[3]; + uint8_t speed = map(SEGMENT.speed, 0, 255, 0, 60); // Updates per second + if (!speed || strip.now - SEGENV.step < 1000 / speed) return FRAMETIME; // Not enough time passed - // Count neighbors and store indexes, get neighbor colors later if needed - for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // Iterate through all neighbors - if (i == 0 && j == 0) continue; // Ignore self - if (i == 1 && j == 0 && !cellValue && !neighbors) break; // Cell can't be born with no neighbors and 2 remaining checks - int nX = x + i; - int nY = y + j; - if (nX < 0) {if (disableWrap) continue; nX = cols - 1;} - else if (nX >= cols) {if (disableWrap) continue; nX = 0;} - if (nY < 0) {if (disableWrap) continue; nY = rows - 1;} - else if (nY >= rows) {if (disableWrap) continue; nY = 0;} + uint8_t blur = map(SEGMENT.custom2, 0, 255, 255, 0); + uint8_t sway = SEGMENT.custom3; - unsigned nIndex = nY * cols + nX; // Neighbor cell index - if (getBitValue(cells, nIndex)) { - ++neighbors; - if (neighbors > 3) break; // Cell dies, stop neighbor loop - neighborIndexes[neighbors - 1] = nIndex; // Store alive neighbor index + // Despawn snow + bool overflow = SEGENV.aux0 && SEGMENT.check3; + int despawnChance = SEGMENT.custom1 == 255 ? 256 : map(SEGMENT.custom1, 0, 255, 0, 100); // 255 goes to 256, allows always despawn + int lastY = rows - 1; + for (int x = 0; x < cols; x++) { + if (overflow || random8() < despawnChance) setBitValue(grid, lastY * cols + x, 0); + if (overlay || getBitValue(grid, lastY * cols + x)) continue; // Skip drawing if inverted overlay or snow + SEGMENT.blendPixelColorXY(x, lastY, bgColor, blur); + } + if (SEGENV.aux0) --SEGENV.aux0; // Decrease overflow + + // Precompute shuffled indices, helps randomize snow movement + uint16_t shuffledIndices[cols]; + for (int i = 0; i < cols; i++) shuffledIndices[i] = i; + std::random_shuffle(shuffledIndices, shuffledIndices + cols); + + // Update snow, loop from 2nd bottom row to top with precomputed random order + for (int y = rows - 2; y >= 0; y--) { + for (int i = 0; i < cols; i++) { + int x = shuffledIndices[i]; + + int pos = XY(x, y); + + uint32_t xyColor = SEGMENT.getPixelColorXY(x, y); // Limit getPixelColorXY calls + if (!getBitValue(grid, pos)) { // No snow, fade if needed and skip + if (!overlay && blur) SEGMENT.setPixelColorXY(x, y, color_blend(xyColor, bgColor, blur)); + continue; } - } - if (!cellValue && neighbors != 3 && cellColor == bgColor) continue; // Skip dead cells with no neighbors and no color + int newX = x, newY = y + 1; + int newPos = XY(newX, newY); + // Open Position Booleans + bool down = !getBitValue(grid, newPos); + bool downLeft = x > 0 && !getBitValue(grid, newPos - 1); + bool downRight = x < cols - 1 && !getBitValue(grid, newPos + 1); - // Rules of Life - if (cellValue && (neighbors < 2 || neighbors > 3)) { - // Loneliness or Overpopulation - setBitValue(futureCells, cIndex, false); - if (!overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur)); - } - else if (neighbors == 3 && !cellValue) { - // Reproduction - // Get Colors - uint32_t nColors[3]; - for (int i = 0; i < 3; i++) { - unsigned nIndex = neighborIndexes[i]; - if (!getBitValue(futureCells, nIndex)) continue; // Parent just died, color lost or blended - uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols); - if (nColor == bgColor) continue; - color = nColor; // Update last seen color - nColors[colorCount++] = nColor; + if (!down) { + if (downLeft && downRight) newX = random8(2) ? x - 1 : x + 1; + else if (downLeft) newX = x - 1; + else if (downRight) newX = x + 1; + else newY = y; // Snow is stuck + } + else if (sway && random8(30) < sway) { // Sway falling snow if horizontal and diagonal directions are open + if (x % 2 == 1 && downLeft && !getBitValue(grid, pos - 1)) newX = x - 1; // Odd Columns Move Left + else if (x % 2 == 0 && downRight && !getBitValue(grid, pos + 1)) newX = x + 1; // Even Columns Move Right + } - } - setBitValue(futureCells, cIndex, true); - uint32_t birthColor = colorCount ? nColors[random8(colorCount)] : color; // Uses last seen color if no surviving neighbors - // Mutate color chance - if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y, birthColor); + if (newY != y || newX != x) { // Snow moved + setBitValue(grid, pos, 0); // Clear old + setBitValue(grid, XY(newX, newY), 1); // Set new + if (!overlay) SEGMENT.setPixelColorXY(x, y, color_blend(xyColor, bgColor, blur)); // Fade old + } + if (!overlay) SEGMENT.setPixelColorXY(newX, newY, xyColor); // Draw new / redraw stuck } - else { // Blur dead cells and redraw alive cells - if (cellValue) SEGMENT.setPixelColorXY(x, y, cellColor == bgColor ? color : cellColor); // Redraw alive, fixes fading cells - else if (blur != 255 && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x, y, color_blend(cellColor, bgColor, blur)); - } - } - //update cell values - memcpy(cells, futureCells, dataSize); - // Get current crc value - uint16_t crc = crc16((const unsigned char*)cells, dataSize); + // Spawn snow + int spawnChance = map(SEGMENT.intensity, 0, 255, 0, 100); + for (int x = 0; x < cols; x++) { // y = 0 + if (random8() >= spawnChance) continue; + if (getBitValue(grid, x)) {SEGENV.aux0 = rows; continue;} // Snow exists, overflowing - bool repetition = false; - if (!aliveCount || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values - if (repetition) { - generation = 0; // reset on next call - SEGENV.step += 1000; // pause final generation for 1 second - return FRAMETIME; + setBitValue(grid, x, 1); // Spawn snow + if (overlay) continue; // Skip drawing if inverted overlay + + if (SEGMENT.check1) SEGMENT.setPixelColorXY(x, 0, ColorFromPalette(SEGPALETTE, random8())); // Use palette + else {int c = random8(120,200); SEGMENT.setPixelColorXY(x, 0, c, c, c);} // Use snow color } - // Update CRC values - if (generation % 16 == 0) *oscillatorCRC = crc; - if (*gliderLength && generation % *gliderLength == 0) *spaceshipCRC = crc; - if (aliveCount == 5) *soloGlider = true; else *soloGlider = false; - - generation++; + SEGENV.step = strip.now; return FRAMETIME; -} // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1"; +} // mode_2DSnowFall() +static const char _data_FX_MODE_2DSNOWFALL[] PROGMEM = "Snow Fall ☾@!,Spawn Rate,Despawn Rate,Blur,Sway Chance,Use Palette,Inverted Overlay,Prevent Overflow,;!,!;!;2;sx=128,ix=16,c1=17,c2=0,c3=0,o1=0,o2=0,o3=1"; ///////////////////////// // 2D Hiphotic // @@ -5388,7 +5735,7 @@ uint16_t mode_2DHiphotic() { // By: ldirko https://edit for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8(cos8(x * SEGMENT.speed/16 + a / 3) + sin8(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); + 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)); } } @@ -5424,6 +5771,7 @@ uint16_t mode_2DJulia(void) { // An animated Julia set float imAg; if (SEGENV.call == 0) { // Reset the center if we've just re-started this animation. + SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); // WLEDMM avoids dimming when blur option is selected julias->xcen = 0.; julias->ycen = 0.; julias->xymag = 1.0; @@ -5434,8 +5782,11 @@ uint16_t mode_2DJulia(void) { // An animated Julia set SEGMENT.intensity = 24; } - julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.f; - julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.f; + // WLEDMM limit drift, so we don't move away into nothing + constexpr float maxCenter = 2.5f; // just an educated guess + if (fabsf(julias->xcen) < maxCenter) julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.f; + if (fabsf(julias->ycen) < maxCenter) julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.f; + julias->xymag = julias->xymag + (float)((SEGMENT.custom3 - 16)<<3)/100000.f; // reduced resolution slider if (julias->xymag < 0.01f) julias->xymag = 0.01f; if (julias->xymag > 1.0f) julias->xymag = 1.0f; @@ -5460,15 +5811,17 @@ uint16_t mode_2DJulia(void) { // An animated Julia set maxIterations = SEGMENT.intensity/2; - // Resize section on the fly for some animaton. + // Resize section on the fly for some animation. reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sinf((float)strip.now/305.f)/20.f; - imAg += sinf((float)strip.now/405.f)/20.f; + //reAl += sinf((float)strip.now/305.f)/20.f; + //imAg += sinf((float)strip.now/405.f)/20.f; + reAl += (float)sin16_t(strip.now * 34) / 655340.f; + imAg += (float)sin16_t(strip.now * 26) / 655340.f; - dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. - dy = (ymax - ymin) / (rows); + dx = (xmax - xmin) / cols; // Scale the delta x and y values to our matrix size. + dy = (ymax - ymin) / rows; // Start y float y = ymin; @@ -5507,11 +5860,23 @@ uint16_t mode_2DJulia(void) { // An animated Julia set } y += dy; } -// SEGMENT.blur(64); + + // WLEDMM + if(SEGMENT.check1) + SEGMENT.blurRows(48, false); // slight blurr + if(SEGMENT.check2) + SEGMENT.blur(64, true); // strong blurr + if(SEGMENT.check3) { // draw crosshair + int screenX = lroundf((0.5f / maxCenter) * (julias->xcen + maxCenter) * float(cols)); + int screenY = lroundf((0.5f / maxCenter) * (julias->ycen + maxCenter) * float(rows)); + int hair = min(min(cols-1, rows-1)/2, 3); + SEGMENT.drawLine(screenX, screenY-hair, screenX, screenY+hair, GREEN, true); + SEGMENT.drawLine(screenX-hair, screenY, screenX+hair, screenY, GREEN, true); + } return FRAMETIME; } // mode_2DJulia() -static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size;!;!;2;ix=24,c1=128,c2=128,c3=16"; +static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size,Soft Blur,Strong Blur,Show Center;!;!;2;ix=24,c1=128,c2=128,c3=16"; ////////////////////////////// @@ -5532,17 +5897,20 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline int maxLoops = max(192, 4*(cols+rows)); maxLoops = ((maxLoops / 128) +1) * 128; // make sure whe have half or full turns => multiples of 128 for (int i=0; i < maxLoops; i ++) { - float xlocn = float(sin8(phase/2 + (i* SEGMENT.speed)/64)) / 255.0f; // WLEDMM align speed with original effect - float ylocn = float(cos8(phase/2 + i*2)) / 255.0f; + float xlocn = float(sin8_t(phase/2 + (i* SEGMENT.speed)/64)) / 255.0f; // WLEDMM align speed with original effect + float ylocn = float(cos8_t(phase/2 + i*2)) / 255.0f; //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing unsigned palIndex = (256*ylocn) + phase/2 + (i* SEGMENT.speed)/64; - SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation + //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation + // WLEDMM wu_pixel is 50% faster, and still lokks better + SEGMENT.wu_pixel(uint32_t(xlocn * ((cols-1) <<8)), uint32_t(ylocn * ((rows-1) <<8)), + CRGB(SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0))); } } else for (int i=0; i < 256; i ++) { //WLEDMM: stick to the original calculations of xlocn and ylocn - uint_fast8_t xlocn = sin8(phase/2 + (i*SEGMENT.speed)/64); - uint_fast8_t ylocn = cos8(phase/2 + i*2); + uint_fast8_t xlocn = sin8_t(phase/2 + (i*SEGMENT.speed)/64); + uint_fast8_t ylocn = cos8_t(phase/2 + i*2); xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "*2 +1" for proper rounding ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 2" is needed to avoid div/0 in map() SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); @@ -5616,7 +5984,7 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. // spawn new falling code if (random8() <= SEGMENT.intensity || emptyScreen) { - uint8_t spawnX = random8(cols); + uint16_t spawnX = random16(cols); SEGMENT.setPixelColorXY(spawnX, 0, spawnColor); // update hint for next run SEGENV.aux0 = spawnX; @@ -5648,8 +6016,8 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have uint8_t y3 = map(inoise8(strip.now * speed, 25355, 22685), 0, 255, 0, rows-1); // and one Lissajou function - uint8_t x1 = beatsin8(23 * speed, 0, cols-1); - uint8_t y1 = beatsin8(28 * speed, 0, rows-1); + uint8_t x1 = beatsin8_t(23 * speed, 0, cols-1); + uint8_t y1 = beatsin8_t(28 * speed, 0, rows-1); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { @@ -5671,7 +6039,7 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have byte color = dist ? 1000 / dist : 255; // map color between thresholds - if (color > 0 and color < 60) { + if (color > 0 && color < 60) { SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(map(color * 9, 9, 531, 0, 255), false, PALETTE_SOLID_WRAP, 0)); } else { SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0)); @@ -5766,9 +6134,11 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); const float maxRows = (rows <= 32) ? 32.0f : (rows <= 64) ? 64.0f : 128.0f; // WLEDMM safe up to 128x128 - const float minScale = (rows <= 32) ? 12.0f : (rows <= 64) ? 4.6f : 2.1f; // WLEDMM + const float minScale = (rows <= 32) ? 12.0f : (rows <= 64) ? 7.2f : 4.6f; // WLEDMM + const float maxCols = (cols <= 32) ? 32.0f : (cols <= 64) ? 64.0f : 128.0f; // WLEDMM safe up to 128x128 const CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; + const CRGBPalette16 &effectPalette = SEGENV.check1 ? SEGPALETTE : auroraPalette; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -5776,7 +6146,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } float adjustHeight = mapf(rows, 8, maxRows, 28, minScale); // maybe use mapf() ??? // WLEDMM yes! - uint16_t adjScale = map2(cols, 8, 64, 310, 63); + uint16_t adjScale = map2(cols, 8, maxCols, 310, 63); // WLEDMM adjustHeight = max(min(adjustHeight, 28.0f), minScale); // WLEDMM bugfix for larger fixtures adjScale = max(min(adjScale, uint16_t(310)), uint16_t(63)); // WLEDMM @@ -5796,11 +6166,11 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } */ uint16_t _scale = map2(SEGMENT.intensity, 0, 255, 30, adjScale); - byte _speed = map2(SEGMENT.speed, 0, 255, 128, 16); + byte _speed = map2(SEGMENT.speed, 0, 255, 136, 20); //WLEDMM add SuperSync control uint16_t xStart, xEnd, yStart, yEnd; - if (SEGMENT.check1) { //Master (sync on needs to show the whole effect, children only their first panel) + if (SEGMENT.check2) { //Master (sync on needs to show the whole effect, children only their first panel) xStart = strip.panel[0].xOffset; xEnd = strip.panel[0].xOffset + strip.panel[0].width; yStart = strip.panel[0].yOffset; @@ -5818,16 +6188,17 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https for (int x = xStart; x < xEnd; x++) { for (int y = yStart; y < yEnd; y++) { SEGENV.step++; - SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(effectPalette, qsub8( inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), fabsf(rows_2 - (float)y) * adjustHeight))); // WLEDMM } } + if (SEGENV.check3) SEGMENT.blurRows(192); return FRAMETIME; } // mode_2DPolarLights() -static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,SuperSync;;;2"; +static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,Use Palette,SuperSync, Blur;;!;2"; ///////////////////////// @@ -5848,7 +6219,7 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi uint32_t a = strip.now / (18 - SEGMENT.speed / 16); uint16_t x = (a / 14) % cols; - uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); + uint16_t y = map((sin8_t(a * 5) + sin8_t(a * 4) + sin8_t(a * 2)), 0, 765, rows-1, 0); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.blur(1 + (SEGMENT.intensity>>4)); @@ -5875,10 +6246,10 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); byte t1 = strip.now / (257 - SEGMENT.speed); // 20; - byte t2 = sin8(t1) / 4 * 2; + byte t2 = sin8_t(t1) / 4 * 2; for (int i = 0; i < 13; i++) { - byte x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! - byte y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! + byte x = sin8_t(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! + byte y = sin8_t(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom2>>3); @@ -5912,18 +6283,18 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g SEGMENT.blur(blurAmount); // Use two out-of-sync sine waves - uint8_t i = beatsin8(19, kBorderWidth, cols-kBorderWidth); - uint8_t j = beatsin8(22, kBorderWidth, cols-kBorderWidth); - uint8_t k = beatsin8(17, kBorderWidth, cols-kBorderWidth); - uint8_t m = beatsin8(18, kBorderWidth, rows-kBorderWidth); - uint8_t n = beatsin8(15, kBorderWidth, rows-kBorderWidth); - uint8_t p = beatsin8(20, kBorderWidth, rows-kBorderWidth); + uint8_t i = beatsin8_t(19, kBorderWidth, cols-kBorderWidth); + uint8_t j = beatsin8_t(22, kBorderWidth, cols-kBorderWidth); + uint8_t k = beatsin8_t(17, kBorderWidth, cols-kBorderWidth); + uint8_t m = beatsin8_t(18, kBorderWidth, rows-kBorderWidth); + uint8_t n = beatsin8_t(15, kBorderWidth, rows-kBorderWidth); + uint8_t p = beatsin8_t(20, kBorderWidth, rows-kBorderWidth); uint16_t ms = strip.now; - SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, ms/29, 255, LINEARBLEND)); - SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, ms/41, 255, LINEARBLEND)); - SEGMENT.addPixelColorXY(k, p, ColorFromPalette(SEGPALETTE, ms/73, 255, LINEARBLEND)); + if (i>= 8*sharpness; SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); hue = y * 3 + offsetX; - intensity = bri = sin8(y * SEGMENT.intensity/2 + offsetY); + intensity = bri = sin8_t(y * SEGMENT.intensity/2 + offsetY); for (int i=0; i>= 8*sharpness; SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); @@ -6051,9 +6422,9 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht SEGMENT.move(SEGENV.aux0, 1); for (size_t i = 0; i < 8; i++) { - byte x = beatsin8(12 + i, 2, cols - 3); - byte y = beatsin8(15 + i, 2, rows - 3); - CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); + byte x = beatsin8_t(12 + i, 2, cols - 3); + byte y = beatsin8_t(15 + i, 2, rows - 3); + CRGB color = ColorFromPalette(SEGPALETTE, beatsin8_t(12 + i, 0, 255), 255); SEGMENT.addPixelColorXY(x, y, color); if (cols > 24 || rows > 24) { SEGMENT.addPixelColorXY(x+1, y, color); @@ -6087,10 +6458,8 @@ uint16_t mode_2Dcrazybees(void) { int8_t signX, signY; int16_t deltaX, deltaY, error; void aimed(uint_fast16_t w, uint_fast16_t h) { - if (!true) //WLEDMM SuperSync - random16_set_seed(strip.now); - aimX = random8(0, w); - aimY = random8(0, h); + aimX = random8(0, min(UINT8_MAX, int(w))); + aimY = random8(0, min(UINT8_MAX, int(h))); hue = random8(); deltaX = abs(aimX - posX); deltaY = abs(aimY - posY); @@ -6104,13 +6473,12 @@ uint16_t mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - if (true) //WLEDMM SuperSync - random16_set_seed(strip.now); + random16_set_seed(strip.now); //WLEDMM SuperSync SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); for (size_t i = 0; i < n; i++) { - bee[i].posX = random8(0, cols); - bee[i].posY = random8(0, rows); + bee[i].posX = random8(0, min(UINT8_MAX, int(cols))); + bee[i].posY = random8(0, min(UINT8_MAX, int(rows))); bee[i].aimed(cols, rows); } } @@ -6261,6 +6629,9 @@ uint16_t mode_2Dfloatingblobs(void) { if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed blob_t *blob = reinterpret_cast(SEGENV.data); + // WLEDMM fix SEGENV.step in case that timebase jumps + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; + if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; // re-initialise if virtual size changes @@ -6370,8 +6741,27 @@ uint16_t mode_2Dscrollingtext(void) { unsigned maxLen = (SEGMENT.name) ? min(32, (int)strlen(SEGMENT.name)) : 0; // WLEDMM make it robust against too long segment names if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; const bool zero = strchr(text, '0') != nullptr; + bool drawShadow = (SEGMENT.check2); // "shadow" is only needed for overlays to improve readability - if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time + // #ERR = show last error code + if ((strlen(text) > 3) && (strncmp_P(text,PSTR("#ERR"),4) == 0)) { + // read wled error code, and keep it for 30sec max + static byte lastErr = ERR_NONE; // errorFlag cache - we can use a static (global) variable here because the error code is global, too + static unsigned long lastErrTime = 0; // time when lastErr was updated + if ((errorFlag != ERR_NONE) && (lastErr != errorFlag)) { // new error code arrived + lastErr = errorFlag; + lastErrTime = millis(); + } + bool haveError = (lastErr != ERR_NONE) && (millis() - lastErrTime < 30000); // true if we have an "active" error code + if (SEGENV.call < 512) haveError = true; // for testing - initially show "E00" + if ((!haveError) && (errorFlag == ERR_NONE)) lastErr = ERR_NONE; // reset error code + // print error number + if (haveError) sprintf_P(text, PSTR("E%-2.2d"), (int)lastErr); + else sprintf_P(text, PSTR(" ")); + } + + if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#A"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time + if (!strncmp_P(text,PSTR("#D"),2) || !strncmp_P(text,PSTR("#MM"),3) || !strncmp_P(text,PSTR("#HH"),3)) drawShadow = false; // no seconds - no shadow needed char sec[5]= {'\0'}; byte AmPmHour = hour(localTime); boolean isitAM = true; @@ -6388,22 +6778,30 @@ uint16_t mode_2Dscrollingtext(void) { else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); - else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%2d"), (int) strip.getFps()); // WLEDMM - else if ((!strncmp_P(text,PSTR("#AMP"),4)) || (!strncmp_P(text,PSTR("#POW"),4))) sprintf_P(text, PSTR("%3.2fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM + else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%3d"), (int) strip.getFps()); // WLEDMM + else if ((!strncmp_P(text,PSTR("#AMP"),4)) || (!strncmp_P(text,PSTR("#POW"),4))) sprintf_P(text, PSTR("%3.1fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM else sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); - } + } else drawShadow = false; // static text does not require shadow const int numberOfLetters = strlen(text); - if (SEGENV.step < strip.now) { + long delayTime = long(strip.now) - long(SEGENV.step); + if ((delayTime >= 0) || (abs(delayTime) > 1500)) { // WLEDMM keep on scrolling if timebase jumps (supersync, or brightness off, or wifi delay) if ((numberOfLetters * letterWidth) > cols) ++SEGENV.aux0 %= (numberOfLetters * letterWidth) + cols; // offset else SEGENV.aux0 = (cols + (numberOfLetters * letterWidth))/2; - ++SEGENV.aux1 &= 0xFF; // color shift - SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); + SEGENV.aux1 = (SEGENV.aux1 + 1) & 0xFF; // color shift // WLEDMM changed to prevent overflow + SEGENV.step = strip.now + map2(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); if (!SEGMENT.check2) { for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++ ) SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); } + } else { // WLEDMM "repaint" segment to prevent flickering + if (!SEGMENT.check2) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) + SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - SEGMENT.custom1); // slightly reduced "blending" to keep trails visible + } } + + if (SEGENV.check2 && ((numberOfLetters * letterWidth) > cols)) drawShadow = true; // scrolling overlay is easier to read with shadow for (int i = 0; i < numberOfLetters; i++) { if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); @@ -6412,7 +6810,7 @@ uint16_t mode_2Dscrollingtext(void) { col1 = SEGCOLOR(0); col2 = SEGCOLOR(2); } - SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2); + SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2, drawShadow); } return FRAMETIME; @@ -6432,7 +6830,13 @@ uint16_t mode_2Ddriftrose(void) { const float CX = (cols-cols%2)/2.f - .5f; const float CY = (rows-rows%2)/2.f - .5f; - const float L = min(cols, rows) / 2.f; + + unsigned L2 = SEGENV.check3 ? max(cols, rows) : min(cols, rows); // WLEDMM we use "max" to use the complete segment + if (SEGENV.check3 && (abs(int(cols) - int(rows)) < 4)) L2 = L2 * 1.4142f; // WLEDMM make "expand" look a bit bigger on square panels + const float L = L2 / 2.f; + // WLEDMM pre-calculate some values + const uint32_t wu_cols = SEGMENT.virtualWidth() * 256; + const uint32_t wu_rows = SEGMENT.virtualHeight() * 256; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -6441,15 +6845,16 @@ uint16_t mode_2Ddriftrose(void) { SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { - uint32_t x = (CX + (sinf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cosf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); + float angle = float(DEG_TO_RAD) * i * 10; + uint32_t x = int((CX + (sinf(angle) * (beatsin8_t(i, 0, L2)-L))) * 255.f); + uint32_t y = int((CY + (cosf(angle) * (beatsin8_t(i, 0, L2)-L))) * 255.f); + if ((x < wu_cols) && (y < wu_rows)) SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur((SEGMENT.intensity>>4)+1); return FRAMETIME; } -static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;;2"; +static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur,,,,,,Full Expand ☾;;;2"; #endif // WLED_DISABLE_2D @@ -6609,14 +7014,14 @@ uint16_t mode_2DSwirl(void) { SEGMENT.fill(BLACK); } - const uint8_t borderWidth = 2; + constexpr uint8_t borderWidth = 2; SEGMENT.blur(SEGMENT.custom1); - uint8_t i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); - uint8_t j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); - uint8_t ni = (cols - 1) - i; - uint8_t nj = (cols - 1) - j; + unsigned i = beatsin8_t( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); + unsigned j = beatsin8_t( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); + unsigned ni = (cols - 1) - i; + unsigned nj = (cols - 1) - j; uint16_t ms = strip.now; um_data_t *um_data = getAudioData(); @@ -6625,12 +7030,12 @@ uint16_t mode_2DSwirl(void) { // printUmData(); - SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (ms / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); - SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (ms / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); - SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (ms / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); - SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (ms / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); - SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (ms / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); - SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (ms / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); + if (i(SEGENV.data); if (SEGENV.call == 0) { @@ -6715,7 +7120,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0); // map to pixels available in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6763,10 +7168,10 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew //SEGMENT.fade_out(240); // twice? really? SEGMENT.fade_out(253); // 50% - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0); // map to pixels availeable in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6818,7 +7223,7 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. float realVolume = volumeSmth; if (SEGENV.check3 && SEGENV.check2) SEGENV.check2 = false; // only one option if (SEGENV.check2) volumeSmth = soundPressure; - if (SEGENV.check3) volumeSmth = 255.0 - agcSensitivity; + if (SEGENV.check3) volumeSmth = 255.0f - agcSensitivity; SEGMENT.fade_out(253); float sensGain = (float)(SEGMENT.intensity+2) / 257.0f; // min gain = 1/128 @@ -6879,11 +7284,11 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() SEGMENT.fade_out(224); // 6.25% - uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); + uint16_t my_sampleAgc = max(min(volumeSmth, 255.0f), 0.0f); for (size_t i=0; iSEGLEN/2) maxLen = SEGLEN/2; @@ -6970,8 +7375,8 @@ uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } - SEGENV.aux0=SEGENV.aux0+beatsin8(5,0,10); - SEGENV.aux1=SEGENV.aux1+beatsin8(4,0,10); + SEGENV.aux0=SEGENV.aux0+beatsin8_t(5,0,10); + SEGENV.aux1=SEGENV.aux1+beatsin8_t(4,0,10); return FRAMETIME; } // mode_midnoise() @@ -7021,7 +7426,7 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. uint8_t fadeRate = map2(SEGMENT.speed,0,255,200,254); SEGMENT.fade_out(fadeRate); - float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; + float tmpSound2 = volumeRaw * 2.0f * (float)SEGMENT.intensity / 255.0f; int maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. if (maxLen <0) maxLen = 0; if (maxLen >SEGLEN) maxLen = SEGLEN; @@ -7031,8 +7436,8 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } - SEGENV.aux0+=beatsin8(5,0,10); - SEGENV.aux1+=beatsin8(4,0,10); + SEGENV.aux0+=beatsin8_t(5,0,10); + SEGENV.aux1+=beatsin8_t(4,0,10); return FRAMETIME; } // mode_noisemeter() @@ -7094,13 +7499,13 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. //SEGMENT.fadeToBlackBy(32); SEGMENT.fadeToBlackBy(48); - plasmoip->thisphase += beatsin8(6,-4,4); // You can change direction and speed individually. - plasmoip->thatphase += beatsin8(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. + plasmoip->thisphase += beatsin8_t(6,-4,4); // You can change direction and speed individually. + plasmoip->thatphase += beatsin8_t(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. // updated, similar to "plasma" effect - softhack007 uint8_t thisbright = cubicwave8(((i*(1 + (3*SEGMENT.speed/32)))+plasmoip->thisphase) & 0xFF)/2; - thisbright += cos8(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. + thisbright += cos8_t(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. uint8_t colorIndex=thisbright; if (volumeSmth * SEGMENT.intensity / 64 < thisbright) {thisbright = 0;} @@ -7259,6 +7664,8 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; float volumeSmth = *(float*)um_data->u_data[0]; + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); // not sure if necessary @@ -7268,6 +7675,10 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. SEGENV.step = 0; // last pixel color } + #if defined(ARDUINO_ARCH_ESP32) + random16_add_entropy(esp_random() & 0xFFFF); // improves randonmess + #endif + int fadeoutDelay = (256 - SEGMENT.speed) / 24; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(max(SEGMENT.speed, (uint8_t)1)); @@ -7276,10 +7687,25 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. } if ((SEGENV.aux1 < SEGLEN) && (volumeSmth > 1.0f)) SEGMENT.setPixelColor(SEGENV.aux1,SEGENV.step); // "repaint" last pixel after blur + unsigned freqBand = SEGENV.aux0 % 16; uint16_t segLoc = random16(SEGLEN); + + if (SEGENV.check1) { // FreqMap mode : blob location by major frequency + int freqLocn; + unsigned maxLen = (SEGENV.check2) ? max(1, SEGLEN-16): SEGLEN; // usable segment length - leave 16 pixels when embedding "GEQ scan" + freqLocn = roundf((log10f((float)FFT_MajorPeak) - 1.78f) * float(maxLen)/(MAX_FREQ_LOG10 - 1.78f)); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. // WLEDMM proper rounding + if (freqLocn < 1) freqLocn = 0; // avoid underflow + segLoc = (SEGENV.check2) ? freqLocn + freqBand : freqLocn; + } else if (SEGENV.check2) { // GEQ Scanner mode: blob location is defined by frequency band + random offset + float bandWidth = float(SEGLEN) / 16.0f; + int bandStart = roundf(bandWidth * freqBand); + segLoc = bandStart + random16(max(1, int(bandWidth))); + } + segLoc = max(uint16_t(0), min(uint16_t(SEGLEN-1), segLoc)); // fix overflows + if (SEGLEN < 2) segLoc = 0; // WLEDMM just to be sure - unsigned pixColor = (2*fftResult[SEGENV.aux0%16]*240)/max(1, SEGLEN-1); // WLEDMM avoid uint8 overflow, and preserve pixel parameters for redraw - unsigned pixIntensity = min((unsigned)(2.0f*fftResult[SEGENV.aux0%16]), 255U); + unsigned pixColor = (2*fftResult[freqBand]*240)/max(1, SEGLEN-1); // WLEDMM avoid uint8 overflow, and preserve pixel parameters for redraw + unsigned pixIntensity = min((unsigned)(2.0f*fftResult[freqBand]), 255U); if (volumeSmth > 1.0f) { SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity)); @@ -7289,12 +7715,12 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. SEGMENT.blur(max(SEGMENT.intensity, (uint8_t)1)); SEGENV.aux0 ++; SEGENV.aux0 %= 16; // make sure it doesn't cross 16 - SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity)); // repaint center pixel after blur + SEGMENT.addPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity/2)); // repaint center pixel after blur } else SEGMENT.blur(max(SEGMENT.intensity, (uint8_t)1)); // silence - just blur it again - return FRAMETIME_FIXED; + return SEGENV.check2 ? FRAMETIME : (3*FRAMETIME_FIXED/4); // faster updates in GEQ Scanner mode } // mode_blurz() -static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz ☾@Fade rate,Blur;!,Color mix;!;01f;sx=48,ix=127,m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz Plus ☾@Fade rate,Blur,,,,FreqMap ☾,GEQ Scanner ☾,;!,Color mix;!;01f;sx=48,ix=127,m12=7,si=0"; // Pinwheel, Beatsin #endif ///////////////////////// @@ -7545,14 +7971,14 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun int lowerLimit = 80 + 3 * SEGMENT.custom1; // min 80hz-850hz i = lowerLimit!=upperLimit ? mapf(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t } else { - // Musical Scale (logarythmic scaling) + // Musical Scale (logarithmic scaling) float upperLimit = logf(80 + 42 * SEGMENT.custom2); // max 80hz-10Khz float lowerLimit = logf(80 + 3 * SEGMENT.custom1); // min 80hz-850hz float peakMapped = fabsf(lowerLimit - upperLimit)>0.05f ? mapf(logf(FFT_MajorPeak), lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow if (peakMapped > 255) intensity = constrain((320-peakMapped), 0, intensity*100) / 100.0f; // too high: fade away i = constrain(peakMapped, 0, 255); // fix over / underflow } - uint16_t b = 255.0 * intensity; + uint16_t b = 255.0f * intensity; if (b > 255) b=255; color = CHSV(i, 176+(uint8_t)b/4, (uint8_t)b); // implicit conversion to RGB supplied by FastLED } @@ -7594,7 +8020,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0); // map to pixels availeable in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -7683,7 +8109,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac frTemp -=132; // This should give us a base musical note of C3 frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; - uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); + uint16_t i = map(beatsin8_t(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); // i will be always constrained between 0 and 0 if SEGLEN equals 1 i = constrain(i, 0, SEGLEN-1); SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); @@ -7749,15 +8175,37 @@ static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color, #ifndef WLED_DISABLE_2D ///////////////////////// -// ** 2D GEQ // +// 1D / 2D GEQ // ///////////////////////// -uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + +// GEQ helper: either draws 2D, or flattens pixels to 1D +static void setFlatPixelXY(bool flatMode, int x, int y, uint32_t color, unsigned cols, unsigned rows, unsigned offset) { + if ((unsigned(x) >= cols) || (unsigned(y) >= rows)) return; // skip invisible + + if (!flatMode) SEGMENT.setPixelColorXY(x, y, color); // normal 2D + else { + y = rows - y - 1; // reverse y + if (rows > 4) { // center y if we have more than 4 pixels per bar + if (y & 0x01) y = (rows + y) / 2; // center bars: odd pixels to the right + else y = (rows - 1 - y) / 2; // even pixels to the left + } + int pix = x*rows + y + offset; // flatten -> transpose x y so that bars stay bars + if (unsigned(pix) >= SEGLEN) return; // skip invisible + SEGMENT.setPixelColor(pix, color); + } +} + +uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. Flat Mode added by softhack007 + //if (!strip.isMatrix) return mode_static(); // not a 2D set-up, not a problem + bool flatMode = !SEGMENT.is2D() || (SEGMENT.width() < 3) || (SEGMENT.height() < 3); // also use flat mode when less than 3 colums or rows const int NUM_BANDS = map2(SEGMENT.custom1, 0, 255, 1, 16); - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - if ((cols <=1) || (rows <=1)) return mode_static(); // not really a 2D set-up + const int vLength = SEGLEN; // for flat mode + const uint16_t cols = flatMode ? min(max(2, NUM_BANDS), (vLength+1)/2) : SEGMENT.virtualWidth(); + const uint16_t rows = flatMode ? vLength / cols : SEGMENT.virtualHeight(); + const unsigned offset = flatMode ? max(0, (vLength - rows*cols +1) / 2) : 0; // flatmode: always center effect + + if ((cols <=1) || (rows <=1)) return mode_static(); // too small if (!SEGENV.allocateData(cols*sizeof(uint16_t))) return mode_static(); //allocation failed uint16_t *previousBarHeight = reinterpret_cast(SEGENV.data); //array of previous bar heights per frequency band @@ -7816,39 +8264,41 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. colorIndex = map(x, 0, cols-1, 0, 255); //WLEDMM } lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration - uint16_t barHeight = map(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here + uint16_t barHeight = map2(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here // WLEDMM end if (barHeight > rows) barHeight = rows; // WLEDMM map() can "overshoot" due to rounding errors if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up uint32_t ledColor = BLACK; - if ((! SEGMENT.check1) && (barHeight > 0)) { // use faster drawLine when single-color bars are needed + if ((! SEGMENT.check1) && !flatMode && (barHeight > 0)) { // use faster drawLine when single-color bars are needed ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); - SEGMENT.drawLine(int(x), max(0,int(rows)-barHeight-1), int(x), int(rows-1), ledColor, false); // max(0, ...) to prevent negative Y + SEGMENT.drawLine(int(x), max(0,int(rows)-barHeight), int(x), int(rows-1), ledColor, false); // max(0, ...) to prevent negative Y } else { for (int y=0; y < barHeight; y++) { if (SEGMENT.check1) //color_vertical / color bars toggle colorIndex = map(y, 0, rows-1, 0, 255); ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x, rows-1 - y, ledColor); + setFlatPixelXY(flatMode, x, rows-1 - y, ledColor, cols, rows, offset); } } - if ((SEGMENT.intensity < 255) && (previousBarHeight[x] > 0) && (previousBarHeight[x] < rows)) // WLEDMM avoid "overshooting" into other segments - SEGMENT.setPixelColorXY(x, rows - previousBarHeight[x], (SEGCOLOR(2) != BLACK) ? SEGCOLOR(2) : ledColor); + if (!flatMode && (SEGMENT.intensity < 255) && (previousBarHeight[x] > 0) && (previousBarHeight[x] < rows)) // WLEDMM avoid "overshooting" into other segments - disable ripple pixels in 1D mode + setFlatPixelXY(flatMode, x, rows - previousBarHeight[x], (SEGCOLOR(2) != BLACK) ? SEGCOLOR(2) : ledColor, cols, rows, offset); if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect } #ifdef SR_DEBUG + if (!flatMode) { // WLEDMM: abuse top left/right pixels for peak detection debugging SEGMENT.setPixelColorXY(cols-1, 0, (samplePeak > 0) ? GREEN : BLACK); if (samplePeak > 0) SEGMENT.setPixelColorXY(0, 0, GREEN); // WLEDMM end + } #endif return FRAMETIME; } // mode_2DGEQ() -static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ ☾@Fade speed,Ripple decay,# of bands,,,Color bars,Smooth bars ☾;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0"; // Beatsin +static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ ☾@Fade speed,Ripple decay,# of bands,,,Color bars,Smooth bars ☾;!,,Peaks;!;12f;c1=255,c2=64,pal=11,si=0"; // Beatsin ///////////////////////// @@ -7950,7 +8400,7 @@ uint16_t mode_2DAkemi(void) { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) {SEGMENT.setUpLeds(); SEGMENT.fill(BLACK);} + if (SEGENV.call == 0) {SEGMENT.fill(BLACK);} uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; @@ -7991,12 +8441,14 @@ uint16_t mode_2DAkemi(void) { //add geq left and right if (um_data) { - for (int x=0; x < cols/8; x++) { - uint16_t band = x * cols/8; + int xMax = cols/8; + for (int x=0; x < xMax; x++) { + size_t band = map2(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands + uint32_t color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); band = constrain(band, 0, 15); uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); - CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); + barHeight = constrain(barHeight, 0, (rows/2)+1); // map() may overshoot for (int y=0; y < barHeight; y++) { SEGMENT.setPixelColorXY(x, rows/2-y, color); SEGMENT.setPixelColorXY(cols-1-x, rows/2-y, color); @@ -8027,12 +8479,12 @@ uint16_t mode_2Ddistortionwaves() { uint16_t a2 = a/2; uint16_t a3 = a/3; - uint16_t cx = beatsin8(10-speed,0,cols-1)*scale; - uint16_t cy = beatsin8(12-speed,0,rows-1)*scale; - uint16_t cx1 = beatsin8(13-speed,0,cols-1)*scale; - uint16_t cy1 = beatsin8(15-speed,0,rows-1)*scale; - uint16_t cx2 = beatsin8(17-speed,0,cols-1)*scale; - uint16_t cy2 = beatsin8(14-speed,0,rows-1)*scale; + uint16_t cx = beatsin8_t(10-speed,0,cols-1)*scale; + uint16_t cy = beatsin8_t(12-speed,0,rows-1)*scale; + uint16_t cx1 = beatsin8_t(13-speed,0,cols-1)*scale; + uint16_t cy1 = beatsin8_t(15-speed,0,rows-1)*scale; + uint16_t cx2 = beatsin8_t(17-speed,0,cols-1)*scale; + uint16_t cy2 = beatsin8_t(14-speed,0,rows-1)*scale; uint16_t xoffs = 0; for (int x = 0; x < cols; x++) { @@ -8042,17 +8494,17 @@ uint16_t mode_2Ddistortionwaves() { for (int y = 0; y < rows; y++) { yoffs += scale; - byte rdistort = cos8((cos8(((x<<3)+a )&255)+cos8(((y<<3)-a2)&255)+a3 )&255)>>1; - byte gdistort = cos8((cos8(((x<<3)-a2)&255)+cos8(((y<<3)+a3)&255)+a+32 )&255)>>1; - byte bdistort = cos8((cos8(((x<<3)+a3)&255)+cos8(((y<<3)-a) &255)+a2+64)&255)>>1; + byte rdistort = cos8_t((cos8_t(((x<<3)+a )&255)+cos8_t(((y<<3)-a2)&255)+a3 )&255)>>1; + byte gdistort = cos8_t((cos8_t(((x<<3)-a2)&255)+cos8_t(((y<<3)+a3)&255)+a+32 )&255)>>1; + byte bdistort = cos8_t((cos8_t(((x<<3)+a3)&255)+cos8_t(((y<<3)-a) &255)+a2+64)&255)>>1; byte valueR = rdistort+ w* (a- ( ((xoffs - cx) * (xoffs - cx) + (yoffs - cy) * (yoffs - cy))>>7 )); byte valueG = gdistort+ w* (a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 )); byte valueB = bdistort+ w* (a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 )); - valueR = gamma8(cos8(valueR)); - valueG = gamma8(cos8(valueG)); - valueB = gamma8(cos8(valueB)); + valueR = gamma8(cos8_t(valueR)); + valueG = gamma8(cos8_t(valueG)); + valueB = gamma8(cos8_t(valueB)); SEGMENT.setPixelColorXY(x, y, RGBW32(valueR, valueG, valueB, 0)); } @@ -8087,21 +8539,12 @@ uint16_t mode_2Dsoap() { // init if (SEGENV.call == 0) { - if (true) {//WLEDMM SuperSync - random16_set_seed(535); - USER_PRINTF("SuperSync\n"); - } + random16_set_seed(535); //WLEDMM SuperSync SEGENV.setUpLeds(); SEGMENT.fill(BLACK); *noise32_x = random16(); *noise32_y = random16(); *noise32_z = random16(); - } else { - if (!true) { //WLEDMM SuperSync - *noise32_x += mov; - *noise32_y += mov; - *noise32_z += mov; - } } //WLEDMM: changing noise calculation for SuperSync to make it deterministic using strip.now @@ -8109,11 +8552,10 @@ uint16_t mode_2Dsoap() { uint32_t noise32_y_MM = *noise32_y; uint32_t noise32_z_MM = *noise32_z; - if (true) { //WLEDMM SuperSync - noise32_x_MM = *noise32_x + mov * strip.now / 100; //10 fps (original 20-40 fps, depending on realized fps) - noise32_y_MM = *noise32_y + mov * strip.now / 100; - noise32_z_MM = *noise32_z + mov * strip.now / 100; - } + //WLEDMM SuperSync + noise32_x_MM = *noise32_x + mov * strip.now / 100; //10 fps (original 20-40 fps, depending on realized fps) + noise32_y_MM = *noise32_y + mov * strip.now / 100; + noise32_z_MM = *noise32_z + mov * strip.now / 100; for (int i = 0; i < cols; i++) { int32_t ioffset = scale32_x * (i - cols / 2); @@ -8197,12 +8639,13 @@ static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; //Octopus (https://editor.soulmatelights.com/gallery/671-octopus) //Stepko and Sutaburosu // adapted for WLED by @blazoncek +// RadialWave mode added by @softhack007, based on https://editor.soulmatelights.com/gallery/1090-radialwave uint16_t mode_2Doctopus() { if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - const uint8_t mapp = 180 / MAX(cols,rows); + const uint16_t mapp = max(1, 180 / MAX(cols,rows)); // WLEDMM make sure this value is not 0 typedef struct { uint8_t angle; @@ -8233,42 +8676,51 @@ uint16_t mode_2Doctopus() { // re-init if SEGMENT dimensions or offset changed if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows || SEGMENT.custom1 != *offsX || SEGMENT.custom2 != *offsY) { - if (!true) //WLEDMM SuperSync - SEGENV.step = 0; // t SEGENV.aux0 = cols; SEGENV.aux1 = rows; *offsX = SEGMENT.custom1; *offsY = SEGMENT.custom2; - const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; - const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; + const uint16_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; + const uint16_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; for (int x = xStart; x < xEnd; x++) { for (int y = yStart; y < yEnd; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2f(y - C_Y, x - C_X); // avoid 128*atan2()/PI - rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu + int dx = (x - C_X); + int dy = (y - C_Y); + rMap[XY(x, y)].angle = int(40.7436f * atan2f(dy, dx)); // avoid 128*atan2()/PI + //rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu + rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu } } } - if (true) // WLEDMM SuperSync - SEGENV.step = strip.now * (SEGMENT.speed / 32 + 1) / 25; // WLEDMM 40fps - else - SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range + // WLEDMM SuperSync + SEGENV.step = (strip.now * (SEGMENT.speed+15)) / 33 / 25; // WLEDMM 40fps; speed range 0.4 ... 8 + + // speed of motion and color change + uint32_t colorSpeed = SEGENV.step / 2; + uint32_t octoSpeed; + if (SEGMENT.check3) octoSpeed = 4*SEGENV.step/5; // 4/5 = 0.8 for RadialWave mode + else octoSpeed = SEGENV.step/2; // 1/2 = 0.5 for Octopus mode for (int x = xStart; x < xEnd; x++) { for (int y = yStart; y < yEnd; y++) { byte angle = rMap[XY(x,y)].angle; byte radius = rMap[XY(x,y)].radius; - //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); - uint16_t intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); - intensity = map(intensity*intensity, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display - CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); + //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); + uint16_t intensity; + if (SEGMENT.check3) + intensity = sin8_t(octoSpeed + sin8_t(octoSpeed - radius) + angle * (SEGMENT.custom3/4+1)); // RadialWave + else + intensity = sin8_t(sin8_t((angle * 4 - radius) / 4 + octoSpeed) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); // Octopus + //intensity = map(intensity*intensity, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display + intensity = (intensity * intensity) / 255; // WLEDMM same as above, but faster and a bit more accurate + CRGB c = ColorFromPalette(SEGPALETTE, colorSpeed - radius, intensity); SEGMENT.setPixelColorXY(x, y, c); } } return FRAMETIME; } -static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs, SuperSync;;!;2;"; - +static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs, SuperSync,,RadialWave ☾;;!;2;"; //Waving Cell //@Stepko (https://editor.soulmatelights.com/gallery/1704-wavingcells) @@ -8284,7 +8736,7 @@ uint16_t mode_2Dwavingcell() { uint8_t aY = SEGMENT.custom2/16 + 1; uint8_t aZ = SEGMENT.custom3 + 1; for (int x = 0; x < cols; x++) for (int y = 0; y . + @license Licensed under the EUPL-1.2 or later */ ///////////////////////// @@ -8505,12 +8948,12 @@ uint16_t mode_2DPaintbrush() { for (size_t i = 0; i < numLines; i++) { byte bin = map(i,0,numLines,0,15); - byte x1 = beatsin8(max(16,int(SEGMENT.speed))/16*1 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); - byte x2 = beatsin8(max(16,int(SEGMENT.speed))/16*2 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); - byte y1 = beatsin8(max(16,int(SEGMENT.speed))/16*3 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); - byte y2 = beatsin8(max(16,int(SEGMENT.speed))/16*4 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); + byte x1 = beatsin8_t(max(16,int(SEGMENT.speed))/16*1 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); + byte x2 = beatsin8_t(max(16,int(SEGMENT.speed))/16*2 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); + byte y1 = beatsin8_t(max(16,int(SEGMENT.speed))/16*3 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); + byte y2 = beatsin8_t(max(16,int(SEGMENT.speed))/16*4 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); - int length = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); + int length = sqrtf((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); length = map8(fftResult[bin],0,length); if (length > max(1,int(SEGMENT.custom3))) { @@ -8620,6 +9063,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_LIGHTNING, &mode_lightning, _data_FX_MODE_LIGHTNING); addEffect(FX_MODE_ICU, &mode_icu, _data_FX_MODE_ICU); addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); + addEffect(FX_MODE_MULTI_COMET_AR, &mode_multi_comet_ar, _data_FX_MODE_MULTI_COMET_AR); addEffect(FX_MODE_DUAL_LARSON_SCANNER, &mode_dual_larson_scanner, _data_FX_MODE_DUAL_LARSON_SCANNER); addEffect(FX_MODE_RANDOM_CHASE, &mode_random_chase, _data_FX_MODE_RANDOM_CHASE); addEffect(FX_MODE_OSCILLATE, &mode_oscillate, _data_FX_MODE_OSCILLATE); @@ -8767,6 +9211,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_2DSOAP, &mode_2Dsoap, _data_FX_MODE_2DSOAP); addEffect(FX_MODE_2DOCTOPUS, &mode_2Doctopus, _data_FX_MODE_2DOCTOPUS); addEffect(FX_MODE_2DWAVINGCELL, &mode_2Dwavingcell, _data_FX_MODE_2DWAVINGCELL); + addEffect(FX_MODE_2DSNOWFALL, &mode_2DSnowFall, _data_FX_MODE_2DSNOWFALL); addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio diff --git a/wled00/FX.h b/wled00/FX.h index a0f3e90f..5e8ae60a 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -2,24 +2,6 @@ WS2812FX.h - Library for WS2812 LED effects. Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) - Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. Modified for WLED */ @@ -61,20 +43,24 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented #endif /* Not used in all effects yet */ +#define FPS_UNLIMITED 250 +#define FPS_UNLIMITED_AC 0 // WLEDMM upstream uses "0 fps" for unlimited. We support both ways #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) // WLEDMM go faster on ESP32 -#define WLED_FPS 120 -#define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) -#define WLED_FPS_SLOW 60 -#define FRAMETIME_FIXED_SLOW (15) // = 66 FPS => 1000/66 -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() +#define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // WLEDMM support higher framerates (up to 250fps) -- 5/8 = 62% +#define WLED_FPS 120 +#define WLED_FPS_SLOW 60 +#define FRAMETIME_FIXED 24 // used in Blurz, Freqmap, Scrolling text, Colortwinkles, Candle +//#define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) +#define FRAMETIME_FIXED_SLOW (15) // = 66 FPS => 1000/66 // used in Solid #else #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) -#define WLED_FPS_SLOW 42 +#define WLED_FPS_SLOW 42 #define FRAMETIME_FIXED_SLOW (1000/WLED_FPS_SLOW) -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() +//#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // Upstream legacy +#define MIN_SHOW_DELAY (_frametime < 16 ? (_frametime <8? (_frametime <7? (_frametime <6 ? 2 :3) :4) : 8) : 15) // WLEDMM support higher framerates (up to 250fps) #endif /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of @@ -100,8 +86,6 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) -#define MIN_SHOW_DELAY (_frametime < 16 ? (_frametime <8? (_frametime <7? (_frametime <6 ? 2 :3) :4) : 8) : 15) // WLEDMM support higher framerates (up to 250fps) - #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()] @@ -346,13 +330,14 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented // Experimental Audioresponsive modes from WLED-SR // #define FX_MODE_3DSphereMove 189 // experimental WLED-SR "cube" mode #define FX_MODE_POPCORN_AR 190 // WLED-SR audioreactive popcorn -// #define FX_MODE_MULTI_COMET_AR 191 // WLED-SR audioreactive multi-comet +#define FX_MODE_MULTI_COMET_AR 191 // WLED-SR audioreactive multi-comet #define FX_MODE_STARBURST_AR 192 // WLED-SR audioreactive fireworks starburst // #define FX_MODE_PALETTE_AR 193 // WLED-SR audioreactive palette #define FX_MODE_FIREWORKS_AR 194 // WLED-SR audioreactive fireworks #define FX_MODE_GEQLASER 195 // WLED-MM GEQ Laser -#define FX_MODE_2DPAINTBRUSH 196 // WLED-MM GEQ Laser -#define MODE_COUNT 197 +#define FX_MODE_2DPAINTBRUSH 196 // WLED-MM Paintbrush +#define FX_MODE_2DSNOWFALL 197 // WLED-MM Snowfall +#define MODE_COUNT 198 typedef enum mapping1D2D { M12_Pixels = 0, @@ -395,6 +380,8 @@ typedef struct Segment { }; uint8_t grouping, spacing; uint8_t opacity; + uint8_t lastBri; // WLEDMM optimization for black-to-black "transitions" + bool needsBlank; // WLEDMM indicates that Segment needs to be blanked (due to change of mirror / reverse / transpose / spacing) uint32_t colors[NUM_COLORS]; uint8_t cct; //0==1900K, 255==10091K uint8_t custom1, custom2; // custom FX parameters/sliders @@ -404,8 +391,8 @@ typedef struct Segment { bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 }; - uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows - uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + uint16_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows, but we cannot be sure. + uint16_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows, but we cannot be sure. char *name = nullptr; // WLEDMM initialize to nullptr // runtime data @@ -434,7 +421,7 @@ typedef struct Segment { }; size_t _dataLen; // WLEDMM uint16_t is too small static size_t _usedSegmentData; // WLEDMM uint16_t is too small - void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows); // set relative pixel within segment with color - faster, but no error checking!!! + void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows) const; // set relative pixel within segment with color - faster, but no error checking!!! bool _isSimpleSegment = false; // simple = no grouping or spacing - mirror, transpose or reverse allowed bool _isSuperSimpleSegment = false; // superSimple = no grouping or spacing, no mirror - only transpose or reverse allowed @@ -442,9 +429,9 @@ typedef struct Segment { // WLEDMM cache some values that won't change while drawing a frame bool _isValid2D = false; uint8_t _brightness = 255; // final pixel brightness - including transitions and segment opacity - bool _firstFill = true; // dirty HACK support uint16_t _2dWidth = 0; // virtualWidth uint16_t _2dHeight = 0; // virtualHeight + uint16_t _virtuallength = 0; // virtualLength void setPixelColorXY_slow(int x, int y, uint32_t c); // set relative pixel within segment with color - full slow version #else @@ -503,6 +490,8 @@ typedef struct Segment { grouping(1), spacing(0), opacity(255), + lastBri(255), + needsBlank(false), colors{DEFAULT_COLOR,BLACK,BLACK}, cct(127), custom1(DEFAULT_C1), @@ -577,9 +566,9 @@ typedef struct Segment { inline bool hasRGB(void) const { return _isRGB; } inline bool hasWhite(void) const { return _hasW; } inline bool isCCT(void) const { return _isCCT; } - inline uint16_t width(void) const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) + inline uint16_t width(void) const { return (stop > start) ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) inline uint16_t height(void) const { return (stopY > startY) ? (stopY - startY) : 0; } // segment height (if 2D) in physical pixels // WLEDMM make sure its always > 0 - inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels // WLEDMM fishy ... need to double-check if this is correct inline uint16_t groupLength(void) const { return max(1, grouping + spacing); } // WLEDMM length = 0 could lead to div/0 in virtualWidth() and virtualHeight() inline uint8_t getLightCapabilities(void) const { return _capabilities; } @@ -612,12 +601,13 @@ typedef struct Segment { * Safe to call from interrupts and network requests. */ inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) + inline void markForBlank(void) { needsBlank = true; } // WLEDMM serialize "blank" requests, avoid parallel drawing from different task void setUpLeds(void); // set up leds[] array for loseless getPixelColor() // transition functions void startTransition(uint16_t dur); // transition has to start before actual segment values change void handleTransition(void); - uint16_t progress(void); //transition progression between 0-65535 + uint16_t progress(void) const; //transition progression between 0-65535 // WLEDMM method inlined for speed (its called at each setPixelColor) inline uint8_t currentBri(uint8_t briNew, bool useCct = false) { @@ -632,11 +622,16 @@ typedef struct Segment { uint8_t currentMode(uint8_t modeNew); uint32_t currentColor(uint8_t slot, uint32_t colorNew); - CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal) const; void setCurrentPalette(void); // 1D strip - uint16_t virtualLength(void) const; + uint16_t calc_virtualLength(void) const; +#ifndef WLEDMM_FASTPATH + inline uint16_t virtualLength(void) const {return calc_virtualLength();} +#else + inline uint16_t virtualLength(void) const {return _virtuallength;} +#endif void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline @@ -677,12 +672,14 @@ typedef struct Segment { if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } + inline uint16_t calc_virtualWidth() const { return virtualWidth();} inline uint16_t virtualHeight() const { // WLEDMM use fast types, and make function inline uint_fast16_t groupLen = groupLength(); uint_fast16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } + inline uint16_t calc_virtualHeight() const { return virtualHeight();} #else inline uint16_t virtualWidth() const { return(_2dWidth);} // WLEDMM get pre-calculated virtualWidth inline uint16_t virtualHeight() const { return(_2dHeight);} // WLEDMM get pre-calculated virtualHeight @@ -725,10 +722,21 @@ typedef struct Segment { uint32_t scaled_col = (_brightness == 255) ? col : color_fade(col, _brightness); // calculate final color setPixelColorXY_fast(x, y, col, scaled_col, int(_2dWidth), int(_2dHeight)); // call "fast" function - } -} + } + } + inline uint32_t getPixelColorXY(int x, int y) const { + // minimal sanity checks + if (!_isValid2D) return 0; // not active + if ((unsigned(x) >= _2dWidth) || (unsigned(y) >= _2dHeight)) return 0 ; // check if (x,y) are out-of-range - due to 2's complement, this also catches negative values + if (ledsrgb) { + int i = x + y*_2dWidth; // avoid error checking done by XY() - be optimistic about ranges of x and y + return RGBW32(ledsrgb[i].r, ledsrgb[i].g, ledsrgb[i].b, 0); + } + else return getPixelColorXY_part2(x, y, int(_2dWidth), int(_2dHeight)); // call "no ledsrgb" function to retrieve pixel from bus driver + } #else void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color + uint32_t __attribute__((pure)) getPixelColorXY(int x, int y) const { return getPixelColorXY_slow(x,y);} #endif inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } @@ -739,7 +747,8 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } //#endif - uint32_t __attribute__((pure)) getPixelColorXY(int x, int y) const; + uint32_t __attribute__((pure)) getPixelColorXY_part2(int x, int y, int cols, int rows) const; + uint32_t __attribute__((pure)) getPixelColorXY_slow(int x, int y) const; // 2D support functions void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend); inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } @@ -761,7 +770,7 @@ typedef struct Segment { inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false, uint8_t depth = UINT8_MAX) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft, depth); } // automatic inline void drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint32_t fillColor = 0); inline void drawArc(unsigned x0, unsigned y0, int radius, CRGB color, CRGB fillColor = BLACK) { drawArc(x0, y0, radius, RGBW32(color.r,color.g,color.b,0), RGBW32(fillColor.r,fillColor.g,fillColor.b,0)); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0); + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, bool drawShadow = false); inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); //void blur1d(fract8 blur_amount); // blur all rows in 1 dimension @@ -861,6 +870,7 @@ class WS2812FX { // 96 bytes customMappingTableSize(0), //WLEDMM customMappingSize(0), _lastShow(0), + _lastServiceShow(0), _segment_index(0), _mainSegment(0) { @@ -904,7 +914,7 @@ class WS2812FX { // 96 bytes purgeSegments(bool force = false), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), - restartRuntime(), + restartRuntime(bool doReset=true), resetSegments(bool boundsOnly = false), //WLEDMM add boundsOnly makeAutoSegments(bool forceReset = false), fixInvalidSegments(), @@ -945,24 +955,25 @@ class WS2812FX { // 96 bytes milliampsPerLed, cctBlending, getActiveSegmentsNum(void) const, - getFirstSelectedSegId(void) __attribute__((pure)), + __attribute__((pure)) getFirstSelectedSegId(void), getLastActiveSegmentId(void) const, - getActiveSegsLightCapabilities(bool selectedOnly = false) __attribute__((pure)), + __attribute__((pure)) getActiveSegsLightCapabilities(bool selectedOnly = false), setPixelSegment(uint8_t n); inline uint8_t getBrightness(void) const { return _brightness; } - inline uint8_t getMaxSegments(void) const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline uint8_t getSegmentsNum(void) const { return _segments.size(); } // returns currently present segments inline uint8_t getCurrSegmentId(void) const { return _segment_index; } inline uint8_t getMainSegmentId(void) const { return _mainSegment; } - inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count inline uint8_t getTargetFps() const { return _targetFps; } inline uint8_t getModeCount() const { return _modeCount; } + inline static constexpr uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline static constexpr uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count uint16_t ablMilliampsMax, currentMilliamps, getLengthPhysical(void) const, + getLengthPhysical2(void) const, // WLEDMM total length including HUB75, network busses excluded __attribute__((pure)) getLengthTotal(void) const, // will include virtual/nonexistent pixels in matrix //WLEDMM attribute added getFps() const; @@ -975,6 +986,7 @@ class WS2812FX { // 96 bytes now, timebase; uint32_t __attribute__((pure)) getPixelColor(uint_fast16_t) const; // WLEDMM attribute pure = does not have side-effects + uint32_t __attribute__((pure)) getPixelColorRestored(uint_fast16_t i) const;// WLEDMM gets the original color from the driver (without downscaling by _bri) inline uint32_t getLastShow(void) const { return _lastShow; } inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } @@ -1018,10 +1030,10 @@ class WS2812FX { // 96 bytes } panelO; //panelOrientation typedef struct panel_t { - uint8_t xOffset; // x offset relative to the top left of matrix in LEDs. WLEDMM 8 bits/256 is enough - uint8_t yOffset; // y offset relative to the top left of matrix in LEDs. WLEDMM 8 bits/256 is enough - uint8_t width; // width of the panel - uint8_t height; // height of the panel + uint16_t xOffset; // x offset relative to the top left of matrix in LEDs. + uint16_t yOffset; // y offset relative to the top left of matrix in LEDs. + uint16_t width; // width of the panel + uint16_t height; // height of the panel union { uint8_t options; struct { @@ -1063,10 +1075,15 @@ class WS2812FX { // 96 bytes // and color transitions uint32_t _colors_t[3]; // color used for effect (includes transition) uint16_t _virtualSegmentLength; +#ifdef WLEDMM_FASTPATH + segment* _currentSeg = nullptr; // WLEDMM speed up SEGMENT access +#endif std::vector _segments; friend class Segment; + uint32_t getPixelColorXYRestored(uint16_t x, uint16_t y) const; // WLEDMM gets the original color from the driver (without downscaling by _bri) + private: uint16_t _length; uint8_t _brightness; @@ -1099,6 +1116,7 @@ class WS2812FX { // 96 bytes uint16_t customMappingSize; /*uint32_t*/ unsigned long _lastShow; // WLEDMM avoid losing precision + unsigned long _lastServiceShow; // WLEDMM last call of strip.show (timestamp) uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 56d81443..2c7d0fc8 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -1,25 +1,5 @@ /* FX_2Dfcn.cpp contains all 2D utility functions - - LICENSE - The MIT License (MIT) - Copyright (c) 2022 Blaz Kristan (https://blaz.at/home) - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - Parts of the code adapted from WLED Sound Reactive: Copyright (c) 2022 Andrew Tuline, Ewoud Wijma, Harm Aldick */ #include "wled.h" @@ -66,9 +46,26 @@ void WS2812FX::setUpMatrix() { USER_PRINTF("setUpMatrix %d x %d\n", Segment::maxWidth, Segment::maxHeight); + // WLEDMM check if mapping table is necessary (avoiding heap fragmentation) +#if defined(WLED_ENABLE_HUB75MATRIX) + bool needLedMap = (loadedLedmap >0); // ledmap loaded + needLedMap |= WLED_FS.exists(F("/2d-gaps.json")); // gapFile found + needLedMap |= panel.size() > 1; // 2D config: more than one panel + if (panel.size() == 1) { + Panel &p = panel[0]; + needLedMap |= p.serpentine; // panel serpentine + needLedMap |= p.vertical; // panel not horizotal + needLedMap |= p.bottomStart | p.rightStart; // panel not top left, or not left->light + needLedMap |= (p.xOffset > 0) || (p.yOffset > 0); // panel does not start at (0,0) + } +#else + bool needLedMap = true; // always use ledMaps on non-HUB75 builds +#endif + //WLEDMM recreate customMappingTable if more space needed if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight)); // TroyHacks + if (!needLedMap) size = 0; // softhack007 USER_PRINTF("setupmatrix customMappingTable alloc %d from %d\n", size, customMappingTableSize); //if (customMappingTable != nullptr) delete[] customMappingTable; //customMappingTable = new uint16_t[size]; @@ -88,8 +85,9 @@ void WS2812FX::setUpMatrix() { if (customMappingTable != nullptr) customMappingTableSize = size; } - if (customMappingTable != nullptr) { + if ((customMappingTable != nullptr) || (!needLedMap)) { // softhack007 customMappingSize = Segment::maxWidth * Segment::maxHeight; + if (!needLedMap) customMappingSize = 0; // softhack007 // fill with empty in case we don't fill the entire matrix for (size_t i = 0; i< customMappingTableSize; i++) { //WLEDMM use customMappingTableSize @@ -130,11 +128,12 @@ void WS2812FX::setUpMatrix() { releaseJSONBufferLock(); } - uint16_t x, y, pix=0; //pixel + if (needLedMap && customMappingTable != nullptr) { // softhack007 + uint_fast16_t x, y, pix=0; //pixel for (size_t pan = 0; pan < panel.size(); pan++) { Panel &p = panel[pan]; - uint16_t h = p.vertical ? p.height : p.width; - uint16_t v = p.vertical ? p.width : p.height; + uint_fast16_t h = p.vertical ? p.height : p.width; + uint_fast16_t v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ for(size_t i = 0; i < h; i++) { y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j; @@ -146,6 +145,7 @@ void WS2812FX::setUpMatrix() { } } } + } // delete gap array as we no longer need it if (gapTable) {delete[] gapTable; gapTable=nullptr;} // softhack prevent dangling pointer @@ -229,6 +229,17 @@ uint32_t __attribute__((hot)) WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) return busses.getPixelColor(index); } +uint32_t __attribute__((hot)) WS2812FX::getPixelColorXYRestored(uint16_t x, uint16_t y) const { // WLEDMM gets the original color from the driver (without downscaling by _bri) + #ifndef WLED_DISABLE_2D + uint_fast16_t index = (y * Segment::maxWidth + x); //WLEDMM: use fast types + #else + uint16_t index = x; + #endif + if (index < customMappingSize) index = customMappingTable[index]; + if (index >= _length) return 0; + return busses.getPixelColorRestored(index); +} + /////////////////////////////////////////////////////////// // Segment:: routines /////////////////////////////////////////////////////////// @@ -241,14 +252,13 @@ void Segment::startFrame(void) { _isSuperSimpleSegment = !mirror && !mirror_y && (grouping == 1) && (spacing == 0); // fastest - we only draw one pixel per call #ifdef WLEDMM_FASTPATH - _isValid2D = isActive() && is2D(); + //_isValid2D = isActive() && is2D(); + _isValid2D = isActive() && strip.isMatrix && length() > 1; _brightness = currentBri(on ? opacity : 0); // if (reverse_y) _isSimpleSegment = false; // for A/B testing - _2dWidth = is2D() ? calc_virtualWidth() : virtualLength(); _2dHeight = calc_virtualHeight(); - #if 0 && defined(WLED_ENABLE_HUB75MATRIX) - _firstFill = true; // dirty HACK - #endif + _2dWidth = _isValid2D ? calc_virtualWidth() : calc_virtualLength(); + _virtuallength = calc_virtualLength(); #endif } // WLEDMM end @@ -259,7 +269,7 @@ void Segment::startFrame(void) { // Simplified version of Segment::setPixelColorXY - without error checking. Does not support grouping or spacing // * expects scaled color (final brightness) as additional input parameter, plus segment virtualWidth() and virtualHeight() -void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) //WLEDMM +void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) const //WLEDMM { unsigned i = UINT_MAX; bool sameColor = false; @@ -271,7 +281,7 @@ void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, } #if 0 // this is still a dangerous optimization - if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (ledsrgb[i] == CRGB(scaled_col))) return; // WLEDMM looks like nothing to do + if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (mode != FX_MODE_2DSCROLLTEXT) && (ledsrgb[i] == CRGB(scaled_col))) return; // WLEDMM looks like nothing to do #endif // handle reverse and transpose @@ -288,9 +298,9 @@ void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, #endif if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed - // handle mirroring - const int_fast16_t wid_ = stop - start; - const int_fast16_t hei_ = stopY - startY; + // handle mirroring - minimum width/height is 1 !!! + const int_fast16_t wid_ = max(1,stop - start); + const int_fast16_t hei_ = max(1, stopY - startY); if (mirror) { //set the corresponding horizontally mirrored pixel if (transpose) strip.setPixelColorXY_fast(start + x, startY + hei_ - y - 1, scaled_col); else strip.setPixelColorXY_fast(start + wid_ - x - 1, startY + y, scaled_col); @@ -333,7 +343,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: } #if 0 // this is a dangerous optimization - if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (ledsrgb[i] == CRGB(col))) return; // WLEDMM looks like nothing to do + if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (mode != FX_MODE_2DSCROLLTEXT) && (ledsrgb[i] == CRGB(col))) return; // WLEDMM looks like nothing to do #endif if (reverse ) x = cols - x - 1; @@ -351,9 +361,9 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: return; } - const int_fast16_t glen_ = groupLength(); // WLEDMM optimization - const int_fast16_t wid_ = width(); - const int_fast16_t hei_ = height(); + const uint_fast16_t glen_ = groupLength(); // WLEDMM optimization + const uint_fast16_t wid_ = max(uint16_t(1), width()); + const uint_fast16_t hei_ = max(uint16_t(1), height()); x *= glen_; // expand to physical pixels y *= glen_; // expand to physical pixels @@ -382,12 +392,14 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: } } +// WLEDMM setPixelColorXY(float x, float y, uint32_t col, ..) is depricated. use wu_pixel(x,y,col) instead. // anti-aliased version of setPixelColorXY() void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa, bool fast) // WLEDMM some speedups due to fast int and faster sqrt16 { if (Segment::maxHeight==1) return; // not a matrix set-up if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized +#if 0 // deprecated const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -431,10 +443,26 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa, bool fast } else { setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col); } + +#else // replacement using wu_pixel + unsigned px = x * ((virtualWidth()-1) <<8); + unsigned py = y * ((virtualHeight()-1) <<8); + wu_pixel(px, py, CRGB(col)); +#endif } -// returns RGBW values of pixel -uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { +// WLEDMM this function is only called by getPixelColorXY, in case we don't have the ledsrgb buffer! +uint32_t IRAM_ATTR_YN Segment::getPixelColorXY_part2(int x, int y, int cols, int rows) const { + if (reverse ) x = cols - x - 1; + if (reverse_y) y = rows - y - 1; + if (transpose) std::swap(x,y); // swap X & Y if segment transposed + const uint_fast16_t groupLength_ = groupLength(); // WLEDMM small optimization + x *= groupLength_; // expand to physical pixels + y *= groupLength_; // expand to physical pixels + return strip.getPixelColorXYRestored(start + x, startY + y); +} + +uint32_t IRAM_ATTR_YN Segment::getPixelColorXY_slow(int x, int y) const { // WLEDMM fallback for non-fastpath builds if (x<0 || y<0 || !isActive()) return 0; // not active or out-of range if (ledsrgb) { int i = XY(x,y); @@ -443,10 +471,11 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels + const uint_fast16_t groupLength_ = groupLength(); // WLEDMM small optimization + x *= groupLength_; // expand to physical pixels + y *= groupLength_; // expand to physical pixels if (x >= width() || y >= height()) return 0; - return strip.getPixelColorXY(start + x, startY + y); + return strip.getPixelColorXYRestored(start + x, startY + y); } // Blends the specified color with the existing pixel color. @@ -459,15 +488,17 @@ void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t void IRAM_ATTR_YN Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) { // if (!isActive()) return; // not active //WLEDMM sanity check is repeated in getPixelColorXY / setPixelColorXY // if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit //WLEDMM - uint32_t col = getPixelColorXY(x,y); - col = color_add(col, color, fast); - setPixelColorXY(x, y, col); + uint32_t oldCol = getPixelColorXY(x,y); + uint32_t col = color_add(oldCol, color, fast); + if (col != oldCol) setPixelColorXY(x, y, col); } void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { // if (!isActive()) return; // not active //WLEDMM sanity check is repeated in getPixelColorXY / setPixelColorXY - CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade); - setPixelColorXY(x, y, pix); + CRGB pix = CRGB(getPixelColorXY(x,y)); + CRGB oldPix = pix; + pix = pix.nscale8_video(fade); + if (pix != oldPix) setPixelColorXY(int(x), int(y), pix); } // blurRow: perform a blur on a row of a rectangular matrix @@ -783,8 +814,13 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 int y = int(intersectY); if (steep) std::swap(x,y); // temporarily swap if steep // pixel coverage is determined by fractional part of y co-ordinate - setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); - setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); + + // WLEDMM added out-of-bounds check: "unsigned(x) < cols" catches negative numbers _and_ too large values + if ((unsigned(x) < unsigned(cols)) && (unsigned(y) < unsigned(rows))) setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); + int xx = x+int(steep); + int yy = y+int(!steep); + if ((unsigned(xx) < unsigned(cols)) && (unsigned(yy) < unsigned(rows))) setPixelColorXY(xx, yy, color_blend(c, getPixelColorXY(xx, yy), seep, true)); + intersectY += gradient; if (steep) std::swap(x,y); // restore if steep } @@ -805,8 +841,8 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 void Segment::drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint32_t fillColor) { if (!isActive() || (radius <=0)) return; // not active - float minradius = float(radius) - .5; - float maxradius = float(radius) + .5; + float minradius = float(radius) - .5f; + float maxradius = float(radius) + .5f; // WLEDMM pre-calculate values to speed up the loop const int minradius2 = roundf(minradius * minradius); const int maxradius2 = roundf(maxradius * maxradius); @@ -819,17 +855,18 @@ void Segment::drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint const int starty = max(0, int(y0)-radius-1); const int endy = min(height, int(y0)+radius+1); - for (int x=startx; x= minradius2) && (distance2 <= maxradius2)) { - setPixelColorXY(x, y, color); - } else { - if (fillColor != 0) - if (distance2 < minradius2) - setPixelColorXY(x, y, fillColor); + for (int x=startx; x= minradius2) && (distance2 <= maxradius2)) { + setPixelColorXY(x, y, color); + } else { + if (fillColor != 0) + if (distance2 < minradius2) + setPixelColorXY(x, y, fillColor); + } } } } @@ -880,7 +917,7 @@ bool Segment::jsonToPixels(char * name, uint8_t fileNr) { // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM -void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2) { +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, bool drawShadow) { if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries @@ -889,27 +926,51 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, const int font = w*h; CRGB col = CRGB(color); - CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); + CRGBPalette16 grad = CRGBPalette16(col, (col2 != BLACK) ? CRGB(col2) : col); + uint32_t bgCol = SEGCOLOR(1); //if (w<5 || w>6 || h!=8) return; + if (drawShadow) w++; // one more column for shadow on right side for (int i = 0; i= rows) break; // drawing off-screen uint8_t bits = 0; + uint8_t bits_up = 0; // WLEDMM this is the previous line: font[(chr * h) + i -1] switch (font) { - case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font - case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font - case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font - case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font - case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font + case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_4x6[(chr * h) + i -1]); + break; // 5x8 font + case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_5x8[(chr * h) + i -1]); + break; // 5x8 font + case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_6x8[(chr * h) + i -1]); + break; // 6x8 font + case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_7x9[(chr * h) + i -1]); + break; // 7x9 font + case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_5x12[(chr * h) + i -1]); + break; // 5x12 font default: return; } - col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); + if (col2 != BLACK) col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); + uint32_t fgCol = uint32_t(col) & 0x00FFFFFF; // WLEDMM cache color value + for (int j = 0; j= 0 || x0 < cols) && ((bits>>(j+(8-w))) & 0x01)) { // bit set & drawing on-screen - setPixelColorXY(x0, y0, col); + if ((x0 >= 0) || (x0 < cols)) { + if ((bits>>(j+(8-w))) & 0x01) { // bit set & drawing on-screen + setPixelColorXY(x0, y0, fgCol); + } else { + if (drawShadow) { + // WLEDMM + if ((j < (w-1)) && (bits>>(j+(8-w) +1)) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel to the right is set + else if ((j > 0) && (bits>>(j+(8-w) -1)) & 0x01) setPixelColorXY(x0, y0, bgCol);// blank when pixel to the left is set + else if ((bits_up>>(j+(8-w))) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel above is set + } + } } } } @@ -925,12 +986,14 @@ void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; // multiply the intensities by the colour, and saturating-add them to the pixels for (int i = 0; i < 4; i++) { - CRGB led = getPixelColorXY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1)); + int wu_x = (x >> 8) + (i & 1); // WLEDMM precalculate x + int wu_y = (y >> 8) + ((i >> 1) & 1); // WLEDMM precalculate y + CRGB led = getPixelColorXY(wu_x, wu_y); CRGB oldLed = led; led.r = qadd8(led.r, c.r * wu[i] >> 8); led.g = qadd8(led.g, c.g * wu[i] >> 8); led.b = qadd8(led.b, c.b * wu[i] >> 8); - if (led != oldLed) setPixelColorXY(int((x >> 8) + (i & 1)), int((y >> 8) + ((i >> 1) & 1)), led); // WLEDMM don't repaint same color + if (led != oldLed) setPixelColorXY(wu_x, wu_y, led); // WLEDMM don't repaint same color } } #undef WU_WEIGHT diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1f086fe0..462bb7e7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -2,25 +2,6 @@ WS2812FX_fcn.cpp contains all utility functions Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) - Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - Modified heavily for WLED */ #include "wled.h" @@ -296,6 +277,15 @@ void Segment::resetIfRequired() { next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; // setOption(SEG_OPTION_RESET, false); startFrame(); // WLEDMM update cached propoerties + if (isActive() && !freeze) fill(BLACK); // WLEDMM start clean + DEBUG_PRINTLN("Segment reset"); + } else if (needsBlank) { + startFrame(); // WLEDMM update cached propoerties + if (isActive() && !freeze) { + fill(BLACK); // WLEDMM start clean + DEBUG_PRINTLN("Segment blanked"); + needsBlank = false; + } } } @@ -321,7 +311,7 @@ void Segment::setUpLeds() { } } -CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) const { static unsigned long _lastPaletteChange = millis() - 990000; // perhaps it should be per segment //WLEDMM changed init value to avoid pure orange after startup static CRGBPalette16 randomPalette = CRGBPalette16(DEFAULT_COLOR); static CRGBPalette16 prevRandomPalette = CRGBPalette16(CRGB(BLACK)); @@ -462,7 +452,7 @@ void Segment::startTransition(uint16_t dur) { } // transition progression between 0-65535 -uint16_t IRAM_ATTR_YN Segment::progress() { +uint16_t IRAM_ATTR_YN Segment::progress() const { if (!transitional || !_t) return 0xFFFFU; unsigned long timeNow = millis(); if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU; @@ -497,19 +487,19 @@ void Segment::setCurrentPalette() { // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms unsigned long timeMS = millis() - _t->_start; - uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends; + uint16_t noOfBlends = min(64UL, (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends); // WLEDMM limit to 64 blends at once, prevent rollover for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); _currentPalette = _t->_palT; // copy transitioning/temporary palette } } void Segment::handleTransition() { - if (!transitional) return; + if (!transitional || !_t) return; // Early exit if no transition active unsigned long maxWait = millis() + 20; if (mode == FX_MODE_STATIC && next_time > maxWait) next_time = maxWait; if (progress() == 0xFFFFU) { if (_t) { - if (_t->_modeP != mode) markForReset(); + //if (_t->_modeP != mode) markForReset(); // WLEDMM effect transition disabled as it does not work (flashes due to double effect restart) delete _t; _t = nullptr; } @@ -529,7 +519,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t stateChanged = true; // send UDP/WS broadcast - if (stop>start) fill(BLACK); //turn old segment range off // WLEDMM stop > start + if (stop>start) markForBlank(); //turn old segment range off // WLEDMM stop > start if (i2 <= i1) { //disable segment stop = 0; markForReset(); @@ -561,7 +551,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + if (fadeTransition && on) startTransition(strip.getTransition()); // start transition prior to change // WLEDMM only on real change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return true; @@ -574,7 +564,7 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + if (fadeTransition && on) startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast } @@ -628,7 +618,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults, bool sliderDefaultsOnly) { sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) {if (oldPalette==-1) oldPalette = palette; setPalette(sOpt);} else {if (oldPalette!=-1) setPalette(oldPalette); oldPalette = -1;} } } - if (!fadeTransition) markForReset(); // WLEDMM quickfix for effect "double startup" bug. -> only works when "Crossfade" is disabled (led settings) + /*if (!fadeTransition)*/ markForReset(); // WLEDMM quickfix for effect "double startup" bug. stateChanged = true; // send UDP/WS broadcast } } @@ -638,7 +628,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); + if (strip.paletteFade && on) startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -656,13 +646,13 @@ uint16_t Segment::nrOfVStrips() const { if (is2D()) { switch (map1D2D) { case M12_pBar: - vLen = virtualWidth(); + vLen = calc_virtualWidth(); break; case M12_sCircle: //WLEDMM - vLen = (virtualWidth() + virtualHeight()) / 6; // take third of the average width + vLen = (calc_virtualWidth() + calc_virtualHeight()) / 6; // take third of the average width break; case M12_sBlock: //WLEDMM - vLen = (virtualWidth() + virtualHeight()) / 8; // take half of the average width + vLen = (calc_virtualWidth() + calc_virtualHeight()) / 8; // take half of the average width break; } } @@ -701,11 +691,7 @@ class JMapC { if (size > 0) return size; else -#ifndef WLEDMM_FASTPATH - return SEGMENT.virtualWidth() * SEGMENT.virtualHeight(); //pixels -#else return SEGMENT.calc_virtualWidth() * SEGMENT.calc_virtualHeight(); // calc pixel sizes -#endif } void setPixelColor(uint16_t i, uint32_t col) { updatejMapDoc(); @@ -797,11 +783,7 @@ class JMapC { jMapFile.close(); maxWidth++; maxHeight++; -#ifndef WLEDMM_FASTPATH - scale = min(SEGMENT.virtualWidth() / maxWidth, SEGMENT.virtualHeight() / maxHeight); // WLEDMM use native min/max -#else scale = min(SEGMENT.calc_virtualWidth() / maxWidth, SEGMENT.calc_virtualHeight() / maxHeight); // WLEDMM re-calc width/heiht from active settings -#endif dataSize += sizeof(jVectorMap); USER_PRINT("dataSize "); USER_PRINT(dataSize); @@ -841,13 +823,16 @@ constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" constexpr int Pinwheel_Steps_XL = 368; constexpr int Pinwheel_Size_XL = 58; // larger than this -> use "XXL" constexpr int Pinwheel_Steps_XXL = 456; +constexpr int Pinwheel_Size_XXL = 68; // larger than this -> use "LL" +constexpr int Pinwheel_Steps_LL = 592; // 128x64 no holes, 28fps constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians constexpr float Int_to_Rad_XXL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XXL; // conversion: from 0...456 to Radians +constexpr float Int_to_Rad_LL = (DEG_TO_RAD * 360) / Pinwheel_Steps_LL; // conversion: from 0...592 to Radians -constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) +constexpr int Fixed_Scale = 32768; // fixpoint scaling factor (15bit for fraction) // Pinwheel helper function: pixel index to radians static float getPinwheelAngle(int i, int vW, int vH) { @@ -856,8 +841,9 @@ static float getPinwheelAngle(int i, int vW, int vH) { if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; if (maxXY <= Pinwheel_Size_XL) return float(i) * Int_to_Rad_XL; + if (maxXY <= Pinwheel_Size_XXL) return float(i) * Int_to_Rad_XXL; // else - return float(i) * Int_to_Rad_XXL; + return float(i) * Int_to_Rad_LL; } // Pinwheel helper function: matrix dimensions to number of rays static int getPinwheelLength(int vW, int vH) { @@ -866,26 +852,33 @@ static int getPinwheelLength(int vW, int vH) { if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; if (maxXY <= Pinwheel_Size_XL) return Pinwheel_Steps_XL; + if (maxXY <= Pinwheel_Size_XXL) return Pinwheel_Steps_XXL; // else - return Pinwheel_Steps_XXL; + return Pinwheel_Steps_LL; } #endif // 1D strip -uint16_t Segment::virtualLength() const { +uint16_t Segment::calc_virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { - uint16_t vW = virtualWidth(); - uint16_t vH = virtualHeight(); + uint16_t vW = calc_virtualWidth(); + uint16_t vH = calc_virtualHeight(); uint16_t vLen = vW * vH; // use all pixels from segment switch (map1D2D) { case M12_pBar: vLen = vH; break; case M12_pCorner: - case M12_pArc: vLen = max(vW,vH); // get the longest dimension break; + case M12_pArc: + { unsigned vLen2 = vW * vW + vH * vH; // length ^2 + if (vLen2 < UINT16_MAX) vLen = sqrt16(vLen2); // use faster function for 16bit values + else vLen = sqrtf(vLen2); // fall-back to float if bigger + if (vW != vH) vLen++; // round up + } + break; case M12_jMap: //WLEDMM jMap if (jMap) vLen = ((JMapC *)jMap)->length(); @@ -898,7 +891,7 @@ uint16_t Segment::virtualLength() const { if (nrOfVStrips()>1) vLen = max(vW,vH) * 4;//0.5; // get the longest dimension else - vLen = max(vW,vH) * 0.5; // get the longest dimension + vLen = max(vW,vH) * 0.5f; // get the longest dimension break; case M12_sPinwheel: vLen = getPinwheelLength(vW, vH); @@ -909,30 +902,30 @@ uint16_t Segment::virtualLength() const { #endif uint16_t groupLen = groupLength(); uint16_t vLength = (length() + groupLen - 1) / groupLen; - if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + if (mirror && width() > 1) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED // WLEDMM bugfix for pseudo 2d strips return vLength; } //WLEDMM used for M12_sBlock static void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, uint16_t vStrip) { float i2; - if (i<=SEGLEN*0.25) { //top, left to right - i2 = i/(SEGLEN*0.25); + if (i<=SEGLEN*0.25f) { //top, left to right + i2 = i/(SEGLEN*0.25f); x = vW / 2 - vStrip - 1 + i2 * vStrip * 2; y = vH / 2 - vStrip - 1; } - else if (i <= SEGLEN * 0.5) { //right, top to bottom - i2 = (i-SEGLEN*0.25)/(SEGLEN*0.25); + else if (i <= SEGLEN * 0.5f) { //right, top to bottom + i2 = (i-SEGLEN*0.25f)/(SEGLEN*0.25f); x = vW / 2 + vStrip; y = vH / 2 - vStrip - 1 + i2 * vStrip * 2; } - else if (i <= SEGLEN * 0.75) { //bottom, right to left - i2 = (i-SEGLEN*0.5)/(SEGLEN*0.25); + else if (i <= SEGLEN * 0.75f) { //bottom, right to left + i2 = (i-SEGLEN*0.5f)/(SEGLEN*0.25f); x = vW / 2 + vStrip - i2 * vStrip * 2; y = vH / 2 + vStrip; } else if (i <= SEGLEN) { //left, bottom to top - i2 = (i-SEGLEN*0.75)/(SEGLEN*0.25); + i2 = (i-SEGLEN*0.75f)/(SEGLEN*0.25f); x = vW / 2 - vStrip - 1; y = vH / 2 + vStrip - i2 * vStrip * 2; } @@ -968,6 +961,7 @@ void IRAM_ATTR_YN __attribute__((hot)) Segment::setPixelColor(int i, uint32_t co if (i==0) setPixelColorXY(0, 0, col); else { + if (i == virtualLength() - 1) setPixelColorXY(vW-1, vH-1, col); // Last i always fill corner if (!_isSuperSimpleSegment) { // WLEDMM: drawArc() is faster if it's NOT "super simple" as the regular M12_pArc // can do "useSymmetry" to speed things along, but a more complicated segment likey @@ -1124,7 +1118,7 @@ void IRAM_ATTR_YN __attribute__((hot)) Segment::setPixelColor(int i, uint32_t co // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) int x = 0, y = 0; if (virtualHeight()>1) y = i; - if (virtualWidth() >1) x = i; + else if (virtualWidth() >1) x = i; setPixelColorXY(x, y, col); return; } @@ -1232,11 +1226,28 @@ uint32_t __attribute__((hot)) Segment::getPixelColor(int i) const if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); else return getPixelColorXY(0, vH - i -1); break; - case M12_pArc: case M12_pCorner: - // use longest dimension - return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + case M12_pArc: { + if (i < max(vW, vH)) { + return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); // Corner and Arc + break; + } + float minradius = float(i) - 0.1f; + const int minradius2 = roundf(minradius * minradius); + int startX, startY; + if (vW >= vH) {startX = vW - 1; startY = 1;} // Last Column + else {startX = 1; startY = vH - 1;} // Last Row + // Loop through only last row/column depending on orientation + for (int x = startX; x < vW; x++) { + int newX2 = x * x; + for (int y = startY; y < vH; y++) { + int newY2 = y * y; + if (newX2 + newY2 >= minradius2) return getPixelColorXY(x, y); + } + } + return getPixelColorXY(vW-1, vH-1); // Last pixel break; + } case M12_jMap: //WLEDMM jMap if (jMap) return ((JMapC *)jMap)->getPixelColor(i); @@ -1304,7 +1315,7 @@ uint32_t __attribute__((hot)) Segment::getPixelColor(int i) const if (offset < INT16_MAX) i += offset; // WLEDMM if ((i >= stop) && (stop>0)) i -= length(); // WLEDMM avoid negative index (stop = 0 is a possible value) if (i<0) i=0; // WLEDMM just to be 100% sure - return strip.getPixelColor(i); + return strip.getPixelColorRestored(i); } uint8_t Segment::differs(Segment& b) const { @@ -1388,17 +1399,6 @@ void Segment::refreshLightCapabilities() { */ void __attribute__((hot)) Segment::fill(uint32_t c) { if (!isActive()) return; // not active - - #if 0 && defined(WLED_ENABLE_HUB75MATRIX) && defined(WLEDMM_FASTPATH) - // DIRTY HACK - this ignores the first fill(black) in each frame, knowing that HUB75 has already blanked out the display. - if (_firstFill) { - _firstFill = false; - if (c == BLACK) { - if (ledsrgb && ledsrgbSize > 0) memset(ledsrgb, 0, ledsrgbSize); - return; - } - } - #endif const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D @@ -1413,12 +1413,12 @@ void __attribute__((hot)) Segment::fill(uint32_t c) { if (_bri_t < 255) scaled_col = color_fade(c, _bri_t); } // fill 2D segment - for(int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + for(unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { if (simpleSegment) setPixelColorXY_fast(x, y, c, scaled_col, cols, rows); else setPixelColorXY_slow(x, y, c); } } else { // fill 1D strip - for (int x = 0; x < cols; x++) setPixelColor(x, c); + for (unsigned x = 0; x < cols; x++) setPixelColor(int(x), c); } } @@ -1471,8 +1471,8 @@ void __attribute__((hot)) Segment::fade_out(uint8_t rate) { int g2 = G(color2); int b2 = B(color2); - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { + uint32_t color = is2D() ? getPixelColorXY(int(x), int(y)) : getPixelColor(int(x)); if (color == color2) continue; // WLEDMM speedup - pixel color = target color, so nothing to do int w1 = W(color); int r1 = R(color); @@ -1492,8 +1492,8 @@ void __attribute__((hot)) Segment::fade_out(uint8_t rate) { uint32_t colorNew = RGBW32(r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); // WLEDMM if (colorNew != color) { // WLEDMM speedup - do not repaint the same color - if (is2D()) setPixelColorXY(x, y, colorNew); - else setPixelColor(x, colorNew); + if (is2D()) setPixelColorXY(int(x), int(y), colorNew); + else setPixelColor(int(x), colorNew); } } } @@ -1507,11 +1507,13 @@ void __attribute__((hot)) Segment::fadeToBlackBy(uint8_t fadeBy) { // WLEDMM minor optimization if(is2D()) { - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - uint32_t cc = getPixelColorXY(x,y); // WLEDMM avoid RGBW32 -> CRGB -> RGBW32 conversion + for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { + uint32_t cc = getPixelColorXY(int(x),int(y)); // WLEDMM avoid RGBW32 -> CRGB -> RGBW32 conversion uint32_t cc2 = color_fade(cc, scaledown); // fade - //if (cc2 != cc) // WLEDMM only re-paint if faded color is different - disabled - causes problem with text overlay - setPixelColorXY((uint16_t)x, (uint16_t)y, cc2); +#ifdef WLEDMM_FASTPATH + if (cc2 != cc) // WLEDMM only re-paint if faded color is different - normally disabled - causes problem with text overlay +#endif + setPixelColorXY(int(x), int(y), cc2); } } else { for (uint_fast16_t x = 0; x < cols; x++) { @@ -1745,7 +1747,7 @@ void WS2812FX::enumerateLedmaps() { //WLEDMM add segment names to be used as ledmap names uint8_t segment_index = 0; for (segment &seg : _segments) { - if (seg.name != nullptr && strcmp(seg.name, "") != 0) { + if (seg.name != nullptr && strlen(seg.name) > 0) { char fileName[33]; snprintf_P(fileName, sizeof(fileName), PSTR("/lm%s.json"), seg.name); bool isFile = WLED_FS.exists(fileName); @@ -1795,7 +1797,10 @@ void WS2812FX::finalizeInit(void) for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; + if (bus->getStart() + bus->getLength() > MAX_LEDS) { + USER_PRINT("\nError: too many LEDs, max number is "); USER_PRINTLN(MAX_LEDS); + break; + } //RGBW mode is enabled if at least one of the strips is RGBW _hasWhiteChannel |= bus->hasWhite(); //refresh is required to remain off if at least one of the strips requires the refresh. @@ -1869,21 +1874,35 @@ void WS2812FX::service() { if (OTAisRunning) return; // WLEDMM avoid flickering during OTA now = nowUp + timebase; - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - if ((_frametime > 2) && (_frametime < 32) && (nowUp - _lastShow) < (_frametime/2)) return; // WLEDMM experimental - stabilizes frametimes but increases CPU load - else if (nowUp - _lastShow < MIN_SHOW_DELAY) return; // WLEDMM fallback - #else - if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + unsigned long elapsed = nowUp - _lastServiceShow; + #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) // WLEDMM go faster on ESP32 + //if (_suspend) return; + if (elapsed < 2) return; // keep wifi alive + if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC)) { +#if 0 + if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service - delivers higher fps +#else + if ((elapsed+1) < _frametime) return; // code from upstream - stricter on FPS +#endif + } + #else // legacy + if (elapsed < _frametime) return; #endif + bool doShow = false; + unsigned speedLimit = (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC) ? (0.85f * FRAMETIME) : 1; // WLEDMM minimum for effect frametime _isServicing = true; _segment_index = 0; for (segment &seg : _segments) { +#ifdef WLEDMM_FASTPATH + _currentSeg = &seg; +#endif // reset the segment runtime data if needed seg.resetIfRequired(); if (!seg.isActive()) continue; + if (!seg.on && !seg.transitional) continue; // WLEDMM skip disabled segments, unless a crossfade is ongoing // last condition ensures all solid segments are updated at the same time if(nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) // WLEDMM ">=" instead of ">" @@ -1893,7 +1912,7 @@ void WS2812FX::service() { uint16_t frameDelay = FRAMETIME; // WLEDMM avoid name clash with "delay" function if (!seg.freeze) { //only run effect function if not frozen - _virtualSegmentLength = seg.virtualLength(); + _virtualSegmentLength = seg.calc_virtualLength(); _colors_t[0] = seg.currentColor(0, seg.colors[0]); _colors_t[1] = seg.currentColor(1, seg.colors[1]); _colors_t[2] = seg.currentColor(2, seg.colors[2]); @@ -1901,15 +1920,21 @@ void WS2812FX::service() { if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); - +#if 0 // WARNING this would kill _supersync_ + now = millis() + timebase; +#endif seg.startFrame(); // WLEDMM + if (!_triggered && (seg.currentBri(seg.opacity) == 0) && (seg.lastBri == 0)) continue; // WLEDMM skip totally black segments // effect blending (execute previous effect) // actual code may be a bit more involved as effects have runtime data including allocated memory //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); frameDelay = (*_mode[seg.currentMode(seg.mode)])(); + + if (frameDelay < speedLimit) frameDelay = FRAMETIME; // WLEDMM limit effects that want to go faster than target FPS if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; if (seg.transitional && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition + seg.lastBri = seg.currentBri(seg.on ? seg.opacity:0); // WLEDMM remember for next time seg.handleTransition(); } @@ -1920,8 +1945,17 @@ void WS2812FX::service() { _virtualSegmentLength = 0; busses.setSegmentCCT(-1); if(doShow) { +#if 0 && defined(ARDUINO_ARCH_ESP32) // EXPERIMENTAL - enabled this to enforce stricter frametime limits + static unsigned long lastTimeShow = 0; + long tdelta = millis() - lastTimeShow; + if ((lastTimeShow > 0) && (tdelta > 1) && (tdelta < _frametime)) // too early - release CPU to slow down + vTaskDelay((tdelta-1) / portTICK_PERIOD_MS); // "-1" because vTaskDelay() may actually delay longer than requested + lastTimeShow = millis(); +#else yield(); +#endif show(); + _lastServiceShow = nowUp; // WLEDMM use correct timestamp } _triggered = false; _isServicing = false; @@ -1941,6 +1975,12 @@ uint32_t WS2812FX::getPixelColor(uint_fast16_t i) const // WLEDMM fast int types return busses.getPixelColor(i); } +uint32_t WS2812FX::getPixelColorRestored(uint_fast16_t i) const // WLEDMM gets the original color from the driver (without downscaling by _bri) +{ + if (i < customMappingSize) i = customMappingTable[i]; + if (i >= _length) return 0; + return busses.getPixelColorRestored(i); +} //DISCLAIMER //The following function attemps to calculate the current LED power usage, @@ -1985,7 +2025,8 @@ void WS2812FX::estimateCurrentAndLimitBri() { for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) { Bus *bus = busses.getBus(bNum); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + auto btype = bus->getType(); + if (EXCLUDE_FROM_ABL(btype)) continue; // WLEDMM exclude non-ABL and network busses uint16_t len = bus->getLength(); uint32_t busPowerSum = 0; for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED @@ -2038,28 +2079,26 @@ void WS2812FX::show(void) { estimateCurrentAndLimitBri(); - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - unsigned long b4show = millis(); // WLEDMM the time before calling "show" + unsigned long showNow = millis(); // include time needed for busses.show() + #ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32 + uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500 #endif + // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods busses.show(); - unsigned long now = millis(); - unsigned long diff = now - _lastShow; + + unsigned long diff = showNow - _lastShow; uint16_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - _lastShow = b4show; // WLEDMM this is more accurate, however it also increases CPU load - strip.service will run more frequently - #else - _lastShow = now; - #endif + _lastShow = showNow; + _lastServiceShow = showNow; #ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32 - uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500 int64_t diff500 = now500 - _lastShow500; - if ((diff500 > 300) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups) + if ((diff500 > 1) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups) float fpcCurr500 = 500000.0f / float(diff500); if (fpcCurr500 > 2) _cumulativeFps500 = (3 * _cumulativeFps500 + (500.0 * fpcCurr500)) / 4; // average for some smoothing @@ -2090,9 +2129,11 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates - _frametime = 1000 / _targetFps; - if (_frametime < 1) _frametime = 1; // WLEDMM better safe than sorry + if (fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates + //if (fps > 0) _frametime = ((2000 / _targetFps) +1) /2; // with rounding + if (fps > 0) _frametime = 1000 / _targetFps; + else _frametime = 2; // AC WLED compatibility + if (fps >= FPS_UNLIMITED) _frametime = 2; // WLEDMM unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { @@ -2196,7 +2237,20 @@ uint16_t WS2812FX::getLengthPhysical(void) const { // WLEDMM fast int types uint_fast16_t len = 0; for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + auto btype = bus->getType(); + if (EXCLUDE_FROM_ABL(btype)) continue; //exclude HUB75, and non-physical network busses + len += bus->getLength(); + } + return len; +} + +//WLEDMM - getLengthPhysical plus plysical busses not supporting ABL (i.e. HUB75) +uint16_t WS2812FX::getLengthPhysical2(void) const { + uint_fast16_t len = 0; + for (unsigned b = 0; b < busses.getNumBusses(); b++) { + Bus *bus = busses.getBus(b); + auto btype = bus->getType(); + if (IS_VIRTUAL(btype)) continue; len += bus->getLength(); } return len; @@ -2254,8 +2308,15 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, _segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY); } -void WS2812FX::restartRuntime() { - for (segment &seg : _segments) seg.markForReset(); +void WS2812FX::restartRuntime(bool doReset) { + for (segment &seg : _segments) { + if (doReset) { // WLEDMM we prefer not to perform a complete restart of all effects + seg.markForReset(); // seg.resetIfRequired(); // WLEDMM calling this function from webserver context will cause troubles + } else { + seg.next_time = 0; seg.step = 0; + seg.markForBlank(); + } + } } void WS2812FX::resetSegments(bool boundsOnly) { //WLEDMM add boundsonly @@ -2409,7 +2470,7 @@ uint8_t WS2812FX::setPixelSegment(uint8_t n) { uint8_t prevSegId = _segment_index; if (n < _segments.size()) { _segment_index = n; - _virtualSegmentLength = _segments[_segment_index].virtualLength(); + _virtualSegmentLength = _segments[_segment_index].calc_virtualLength(); } return prevSegId; } @@ -2558,6 +2619,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { uint16_t maxHeight = atoi(cleanUpName(fileName)); //DEBUG_PRINTF(" (\"height\": %s) \n", fileName) + #ifndef WLEDMM_NO_MAP_RESET //WLEDMM: support ledmap file properties width and height: if found change segment if (maxWidth * maxHeight > 0) { Segment::maxWidth = maxWidth; @@ -2566,6 +2628,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { } else setUpMatrix(); //reset segment sizes to panels + #endif } USER_PRINTF("deserializeMap %d x %d\n", Segment::maxWidth, Segment::maxHeight); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 4ee77050..ff12b85e 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -11,14 +11,14 @@ // WLEDMM 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 -bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value +inline bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value size_t byteIndex = position / 8; unsigned bitIndex = position % 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 +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 / 8; unsigned bitIndex = position % 8; @@ -162,7 +162,7 @@ void BusDigital::show() { PolyBus::show(_busPtr, _iType); } -bool BusDigital::canShow() const { +bool BusDigital::canShow() { return PolyBus::canShow(_busPtr, _iType); } @@ -452,7 +452,7 @@ uint8_t BusOnOff::getPins(uint8_t* pinArray) const { } -BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { +BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) { _valid = false; USER_PRINT("["); switch (bc.type) { @@ -461,6 +461,11 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _UDPtype = 2; USER_PRINT("NET_ARTNET_RGB"); break; + case TYPE_NET_ARTNET_RGBW: + _rgbw = true; + _UDPtype = 2; + USER_PRINT("NET_ARTNET_RGBW"); + break; case TYPE_NET_E131_RGB: _rgbw = false; _UDPtype = 1; @@ -473,37 +478,84 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { break; } _UDPchannels = _rgbw ? 4 : 3; - _data = (byte *)malloc(bc.count * _UDPchannels); + #ifdef ESP32 + _data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); + #else + _data = (byte*) calloc((bc.count * _UDPchannels)+15, sizeof(byte)); + #endif if (_data == nullptr) return; - memset(_data, 0, bc.count * _UDPchannels); _len = bc.count; + _colorOrder = bc.colorOrder; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); _broadcastLock = false; _valid = true; - USER_PRINTF(" %u.%u.%u.%u] \n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); + _artnet_outputs = bc.artnet_outputs; + _artnet_leds_per_output = bc.artnet_leds_per_output; + _artnet_fps_limit = max(uint8_t(1), bc.artnet_fps_limit); + USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); } -void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { - if (!_valid || pix >= _len) return; - if (hasWhite()) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT - uint16_t offset = pix * _UDPchannels; - _data[offset] = R(c); - _data[offset+1] = G(c); - _data[offset+2] = B(c); - if (_rgbw) _data[offset+3] = W(c); +void IRAM_ATTR_YN BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { + if (!_valid || pix >= _len) return; + if (_rgbw) c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); // color correction from CCT + + uint16_t offset = pix * _UDPchannels; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder); + + if (_colorOrder != co || _colorOrder != COL_ORDER_RGB) { + switch (co) { + case COL_ORDER_GRB: + _data[offset] = G(c); _data[offset+1] = R(c); _data[offset+2] = B(c); + break; + case COL_ORDER_RGB: + _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); + break; + case COL_ORDER_BRG: + _data[offset] = B(c); _data[offset+1] = R(c); _data[offset+2] = G(c); + break; + case COL_ORDER_RBG: + _data[offset] = R(c); _data[offset+1] = B(c); _data[offset+2] = G(c); + break; + case COL_ORDER_GBR: + _data[offset] = G(c); _data[offset+1] = B(c); _data[offset+2] = R(c); + break; + case COL_ORDER_BGR: + _data[offset] = B(c); _data[offset+1] = G(c); _data[offset+2] = R(c); + break; + } + if (_rgbw) _data[offset+3] = W(c); + } else { + _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); + if (_rgbw) _data[offset+3] = W(c); + } } -uint32_t BusNetwork::getPixelColor(uint16_t pix) const { - if (!_valid || pix >= _len) return 0; - uint16_t offset = pix * _UDPchannels; - return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0); +uint32_t IRAM_ATTR_YN BusNetwork::getPixelColor(uint16_t pix) const { + if (!_valid || pix >= _len) return 0; + uint16_t offset = pix * _UDPchannels; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder); + + uint8_t r = _data[offset + 0]; + uint8_t g = _data[offset + 1]; + uint8_t b = _data[offset + 2]; + uint8_t w = _rgbw ? _data[offset + 3] : 0; + + switch (co) { + case COL_ORDER_GRB: return RGBW32(g, r, b, w); + case COL_ORDER_RGB: return RGBW32(r, g, b, w); + case COL_ORDER_BRG: return RGBW32(b, r, g, w); + case COL_ORDER_RBG: return RGBW32(r, b, g, w); + case COL_ORDER_GBR: return RGBW32(g, b, r, w); + case COL_ORDER_BGR: return RGBW32(b, g, r, w); + default: return RGBW32(r, g, b, w); // default to RGB order + } } void BusNetwork::show() { if (!_valid || !canShow()) return; _broadcastLock = true; - realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw); + realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw, _artnet_outputs, _artnet_leds_per_output, _artnet_fps_limit); _broadcastLock = false; } @@ -526,12 +578,108 @@ void BusNetwork::cleanup() { #ifdef WLED_ENABLE_HUB75MATRIX #warning "HUB75 driver enabled (experimental)" +// BusHub75Matrix "global" variables (static members) +MatrixPanel_I2S_DMA* BusHub75Matrix::activeDisplay = nullptr; +VirtualMatrixPanel* BusHub75Matrix::activeFourScanPanel = nullptr; +HUB75_I2S_CFG BusHub75Matrix::activeMXconfig = HUB75_I2S_CFG(); +uint8_t BusHub75Matrix::activeType = 0; +uint8_t BusHub75Matrix::instanceCount = 0; +uint8_t BusHub75Matrix::last_bri = 0; + + +// -------------------------- +// Bitdepth reduction based on panel size +// -------------------------- +#if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + // esp32-S3 with octal PSRAM + #if defined(SPIRAM_FRAMEBUFFER) + // when PSRAM is used for pixel buffers + #define MAX_PIXELS_8BIT (192 * 64) + #define MAX_PIXELS_6BIT ( 64 * 64) // trick: skip this category, so we go directly from 8bit to 4bit + #define MAX_PIXELS_4BIT (256 * 128) + #else + // PSRAM not used for pixel buffers + #define MAX_PIXELS_8BIT (128 * 64) + #define MAX_PIXELS_6BIT (192 * 64) + #define MAX_PIXELS_4BIT (256 * 64) + #endif +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) + // standard esp32-S3 with quad PSRAM + #define MAX_PIXELS_8BIT ( 96 * 64) + #define MAX_PIXELS_6BIT (128 * 64) + #define MAX_PIXELS_4BIT (160 * 64) +#elif defined(CONFIG_IDF_TARGET_ESP32S3) + // HD-WF2 is an esp32-S3 without PSRAM - use same limits as classic esp32 + #define MAX_PIXELS_8BIT ( 64 * 64) + #define MAX_PIXELS_6BIT ( 96 * 64) + #define MAX_PIXELS_4BIT (128 * 64) +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + // esp32-S2 only has 320KB RAM + #define MAX_PIXELS_8BIT ( 48 * 48) + #define MAX_PIXELS_6BIT ( 64 * 48) + #define MAX_PIXELS_4BIT ( 96 * 64) +#else + // classic esp32, and anything else + #define MAX_PIXELS_8BIT ( 64 * 64) + #define MAX_PIXELS_6BIT ( 96 * 64) + #define MAX_PIXELS_4BIT (128 * 64) +#endif +// -------------------------- + BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + MatrixPanel_I2S_DMA* display = nullptr; + VirtualMatrixPanel* fourScanPanel = nullptr; + HUB75_I2S_CFG mxconfig; + size_t lastHeap = ESP.getFreeHeap(); _valid = false; - mxconfig.double_buff = false; // default to off, known to cause issue with some effects but needs more memory + _len = 0; - fourScanPanel = nullptr; + // allow exactly one instance + if (instanceCount > 0) { + USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! already active - preventing attempt to create more than one driver instance."); + return; + } + + mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer + + // mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver + // mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel + + // mxconfig.latch_blanking = 1; // needed for some ICS panels + // mxconfig.latch_blanking = 3; // use in case you see gost images + // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz + // mxconfig.min_refresh_rate = 90; + + mxconfig.clkphase = bc.reversed; + if (bc.refreshReq) mxconfig.latch_blanking = 1; // needed for some ICS panels (default = 2) + // fake bus flags + _needsRefresh = mxconfig.latch_blanking == 1; + reversed = mxconfig.clkphase; + + if (bc.type > 104) mxconfig.driver = HUB75_I2S_CFG::FM6124; // use FM6124 for "outdoor" panels - workaround until we can make the driver user-configurable + + // How many panels we have connected, cap at sane value, prevent bad data preventing boot due to low memory + #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) // ESP32-S3: allow up to 6 panels + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 6)); + #elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2: only 2 panels due to small RAM + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 2)); + #else // others: up to 4 panels + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 4)); + #endif + + #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) + if(bc.pins[0] > 4) { + USER_PRINTLN("WARNING, chain limited to 4"); + } + # else + // Disable this check if you are want to try bigger setups and accept you + // might need to do full erase to recover from memory relayed boot-loop if you push too far + if(mxconfig.mx_height >= 64 && (bc.pins[0] > 1)) { + USER_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory"); + //mxconfig.chain_length = 1; + } + #endif switch(bc.type) { case 101: @@ -546,6 +694,10 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.mx_width = 64; mxconfig.mx_height = 64; break; + case 104: + mxconfig.mx_width = 128; + mxconfig.mx_height = 64; + break; case 105: mxconfig.mx_width = 32 * 2; mxconfig.mx_height = 32 / 2; @@ -558,14 +710,19 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.mx_width = 64 * 2; mxconfig.mx_height = 64 / 2; break; + case 108: // untested + mxconfig.mx_width = 128 * 2; + mxconfig.mx_height = 64 / 2; + break; } - if(mxconfig.mx_height >= 64 && (bc.pins[0] > 1)) { - USER_PRINT("WARNING, only single panel can be used of 64 pixel boards due to memory"); - mxconfig.chain_length = 1; - } + // reduce bitdepth based on total pixels + unsigned numPixels = mxconfig.mx_height * mxconfig.mx_width * mxconfig.chain_length; + if (numPixels <= MAX_PIXELS_8BIT) mxconfig.setPixelColorDepthBits(8); // 24bit + else if (numPixels <= MAX_PIXELS_6BIT) mxconfig.setPixelColorDepthBits(6); // 18bit + else if (numPixels <= MAX_PIXELS_4BIT) mxconfig.setPixelColorDepthBits(4); // 12bit + else mxconfig.setPixelColorDepthBits(3); // 9bit - // mxconfig.driver = HUB75_I2S_CFG::SHIFTREG; #if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 @@ -573,8 +730,6 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config"); - //mxconfig.double_buff = true; // <------------- Turn on double buffer - mxconfig.gpio.r1 = 42; mxconfig.gpio.g1 = 41; mxconfig.gpio.b1 = 40; @@ -592,7 +747,36 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.gpio.d = 35; mxconfig.gpio.e = 21; -#elif defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM + + #if defined(MOONHUB_S3_PINOUT) + USER_PRINTLN("MatrixPanel_I2S_DMA - T7 S3 with PSRAM, MOONHUB pinout"); + + // HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN}; + mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 }; + + #else + USER_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM"); + + mxconfig.gpio.r1 = 1; + mxconfig.gpio.g1 = 2; + mxconfig.gpio.b1 = 42; + // 4th pin is GND + mxconfig.gpio.r2 = 41; + mxconfig.gpio.g2 = 40; + mxconfig.gpio.b2 = 39; + mxconfig.gpio.e = 38; + mxconfig.gpio.a = 45; + mxconfig.gpio.b = 48; + mxconfig.gpio.c = 47; + mxconfig.gpio.d = 21; + mxconfig.gpio.clk = 18; + mxconfig.gpio.lat = 8; + mxconfig.gpio.oe = 3; + // 16th pin is GND + #endif + +#elif defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 HD-WF2 // Huidu HD-WF2 ESP32-S3 // https://www.aliexpress.com/item/1005002258734810.html @@ -700,21 +884,55 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh #endif - // mxconfig.double_buff = true; // <------------- Turn on double buffer - // mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver - //mxconfig.latch_blanking = 3; - // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz - //mxconfig.min_refresh_rate = 90; - //mxconfig.min_refresh_rate = 120; - mxconfig.clkphase = false; // can help in case that the leftmost column is invisible, or pixels on the right side "bleeds out" to the left. + USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u (type %u) length: %u, %u bits/pixel.\n", mxconfig.mx_width, mxconfig.mx_height, bc.type, mxconfig.chain_length, mxconfig.getPixelColorDepthBits() * 3); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); lastHeap = ESP.getFreeHeap(); - - mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[0], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory - - USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + // check if we can re-use the existing display driver + if (activeDisplay) { + if ( (memcmp(&(activeMXconfig.gpio), &(mxconfig.gpio), sizeof(mxconfig.gpio)) != 0) // other pins? + || (activeMXconfig.chain_length != mxconfig.chain_length) // other chain length? + || (activeMXconfig.mx_width != mxconfig.mx_width) || (activeMXconfig.mx_height != mxconfig.mx_height) // other size? + || (bc.type != activeType) // different panel type ? + || (activeMXconfig.clkphase != mxconfig.clkphase) // different driver options ? + || (activeMXconfig.latch_blanking != mxconfig.latch_blanking) + || (activeMXconfig.i2sspeed != mxconfig.i2sspeed) + || (activeMXconfig.driver != mxconfig.driver) + || (activeMXconfig.min_refresh_rate != mxconfig.min_refresh_rate) + || (activeMXconfig.getPixelColorDepthBits() != mxconfig.getPixelColorDepthBits()) ) + { + // not the same as before - delete old driver + DEBUG_PRINTLN("MatrixPanel_I2S_DMA deleting old driver!"); + activeDisplay->stopDMAoutput(); + delay(28); + //#if !defined(CONFIG_IDF_TARGET_ESP32S3) // prevent crash + delete activeDisplay; + //#endif + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + #if defined(CONFIG_IDF_TARGET_ESP32S3) // runtime reconfiguration is not working on -S3 + USER_PRINTLN("\n\n****** MatrixPanel_I2S_DMA !KABOOM WARNING! Reboot needed to change driver options ***********\n"); + errorFlag = ERR_REBOOT_NEEDED; + #endif + } + } // OK, now we can create our matrix object - display = new MatrixPanel_I2S_DMA(mxconfig); + bool newDisplay = false; // true when the previous display object wasn't re-used + if (!activeDisplay) { + display = new MatrixPanel_I2S_DMA(mxconfig); // create new matrix object + newDisplay = true; + } else { + display = activeDisplay; // continue with existing matrix object + fourScanPanel = activeFourScanPanel; + } + + if (display == nullptr) { + USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + return; + } this->_len = (display->width() * display->height()); @@ -739,58 +957,78 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("MatrixPanel_I2S_DMA created"); // let's adjust default brightness - display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% - _bri = 25; + //display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin() + _bri = (last_bri > 0) ? last_bri : 25; // try to restore persistent brightness value delay(24); // experimental + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(int(lastHeap - ESP.getFreeHeap())); // Allocate memory and start DMA display - if( not display->begin() ) { + if (newDisplay && (display->begin() == false)) { USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + _valid = false; return; } else { - USER_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + if (newDisplay) { USER_PRINTLN("MatrixPanel_I2S_DMA begin, started ok"); } + else { USER_PRINTLN("MatrixPanel_I2S_DMA begin, using existing display."); } + + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle _valid = true; + display->setBrightness8(_bri); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin() display->clearScreen(); // initially clear the screen buffer USER_PRINTLN("MatrixPanel_I2S_DMA clear ok"); if (_ledBuffer) free(_ledBuffer); // should not happen if (_ledsDirty) free(_ledsDirty); // should not happen - USER_PRINTLN("MatrixPanel_I2S_DMA allocate memory"); - _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits - USER_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok"); - - if (_ledsDirty == nullptr) { - display->stopDMAoutput(); - delete display; display = nullptr; - _valid = false; - USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); - return; // fail is we cannot get memory for the buffer - } - setBitArray(_ledsDirty, _len, false); // reset dirty bits - if (mxconfig.double_buff == false) { + _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + if (_ledsDirty) setBitArray(_ledsDirty, _len, false); // reset dirty bits + + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + if (psramFound()) { + _ledBuffer = (CRGB*) ps_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } else { + _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } + #else _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) - } + #endif + } + + if ((_ledBuffer == nullptr) || (_ledsDirty == nullptr)) { + // fail is we cannot get memory for the buffer + errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag + USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for leds buffer!")); + cleanup(); // free buffers, and deallocate pins + _valid = false; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + return; // fail } switch(bc.type) { case 105: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 32x32"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 32, 32); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 32, 32); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); fourScanPanel->setRotation(0); break; case 106: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 64x32"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 32); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 64, 32); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); fourScanPanel->setRotation(0); break; case 107: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_64PX_HIGH"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 64); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 64, 64); + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); + fourScanPanel->setRotation(0); + break; + case 108: // untested + USER_PRINTLN("MatrixPanel_I2S_DMA 128x64 FOUR_SCAN_64PX_HIGH"); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 128, 64); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); fourScanPanel->setRotation(0); break; @@ -803,7 +1041,6 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINT(F("MatrixPanel_I2S_DMA ")); USER_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); - if (mxconfig.double_buff == true) USER_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); if (_ledBuffer != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); if (_ledsDirty != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { @@ -811,9 +1048,19 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); USER_PRINTLN(F(" bytes.")); } + + if (_valid) { + // config is active, copy to global + activeType = bc.type; + activeDisplay = display; + activeFourScanPanel = fourScanPanel; + if (newDisplay) memcpy(&activeMXconfig, &mxconfig, sizeof(mxconfig)); + } + instanceCount++; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); } -void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { +void __attribute__((hot)) IRAM_ATTR BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; // if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT @@ -824,10 +1071,14 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty" } } + #if 0 + // !! this code is not used any more !! + // BusHub75Matrix::BusHub75Matrix will fail if allocating _ledBuffer fails. + // The fallback code below created lots of flickering so it does not make sense to keep it enabled. else { - if ((c == BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black - setBitInArray(_ledsDirty, pix, c != BLACK); // dirty = true means "color is not BLACK" - + // no double buffer allocated --> directly draw pixel + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; #ifndef NO_CIE1931 c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction #endif @@ -847,45 +1098,64 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); } } + #endif } -uint32_t BusHub75Matrix::getPixelColor(uint16_t pix) const { - if (!_valid || pix >= _len) return BLACK; - if (_ledBuffer) - return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours - else - return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not +uint32_t IRAM_ATTR BusHub75Matrix::getPixelColor(uint16_t pix) const { + if (!_valid || pix >= _len || !_ledBuffer) return BLACK; + return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours +} + +uint32_t __attribute__((hot)) IRAM_ATTR BusHub75Matrix::getPixelColorRestored(uint16_t pix) const { + if (!_valid || pix >= _len || !_ledBuffer) return BLACK; + return uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; } void BusHub75Matrix::setBrightness(uint8_t b, bool immediate) { _bri = b; - if (_bri > 238) _bri=238; - display->setBrightness(_bri); + if (!_valid) return; + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + // if (_bri > 238) _bri=238; // not strictly needed. Enable this line if you see glitches at highest brightness. + if ((_bri > 253) && (activeMXconfig.latch_blanking < 2)) _bri=253; // prevent glitches at highest brightness. + last_bri = _bri; + if (display) display->setBrightness(_bri); } -void __attribute__((hot)) BusHub75Matrix::show(void) { +void __attribute__((hot)) IRAM_ATTR BusHub75Matrix::show(void) { if (!_valid) return; + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + if (!display) return; display->setBrightness(_bri); if (_ledBuffer) { // write out buffered LEDs + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; bool isFourScan = (fourScanPanel != nullptr); //if (isFourScan) fourScanPanel->setRotation(0); unsigned height = isFourScan ? fourScanPanel->height() : display->height(); unsigned width = _panelWidth; + // Cache pointers to LED array and bitmask array, to avoid repeated accesses + const byte* ledsDirty = _ledsDirty; + const CRGB* ledBuffer = _ledBuffer; + //while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. size_t pix = 0; // running pixel index for (int y=0; ydrawPixelRGB888(int16_t(x), int16_t(y), r, g, b); else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); } @@ -893,48 +1163,56 @@ void __attribute__((hot)) BusHub75Matrix::show(void) { } setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits } - - if(mxconfig.double_buff) { - display->flipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) - // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. - display->clearScreen(); // Now clear the back-buffer - setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits - } } void BusHub75Matrix::cleanup() { - if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) - _valid = false; - _panelWidth = 0; - deallocatePins(); - USER_PRINTLN("HUB75 output ended."); + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; + if (display) display->clearScreen(); +#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't stop, as we want to re-use the driver later + if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) + _panelWidth = 0; + USER_PRINTLN("HUB75 output ended."); +#else + USER_PRINTLN("HUB75 output paused."); +#endif + + _valid = false; + deallocatePins(); //if (fourScanPanel != nullptr) delete fourScanPanel; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior - delete display; - display = nullptr; - fourScanPanel = nullptr; +#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't delete, as we want to re-use the driver later + if (display) delete display; + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + USER_PRINTLN("HUB75 deleted."); +#else + USER_PRINTLN("HUB75 cleanup done."); +#endif + + if (instanceCount > 0) instanceCount--; if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; } void BusHub75Matrix::deallocatePins() { - pinManager.deallocatePin(mxconfig.gpio.r1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.g1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.r2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.g2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.r1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.g1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.r2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.g2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.lat, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.oe, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.clk, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.lat, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.oe, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.clk, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.a, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.c, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.d, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.e, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.a, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.c, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.d, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.e, PinOwner::HUB75); } #endif @@ -966,14 +1244,14 @@ int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; DEBUG_PRINTF("BusManager::add(bc.type=%u)\n", bc.type); if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) { - busses[numBusses] = new BusNetwork(bc); + busses[numBusses] = new BusNetwork(bc, colorOrderMap); } else if (bc.type >= TYPE_HUB75MATRIX && bc.type <= (TYPE_HUB75MATRIX + 10)) { #ifdef WLED_ENABLE_HUB75MATRIX DEBUG_PRINTLN("BusManager::add - Adding BusHub75Matrix"); busses[numBusses] = new BusHub75Matrix(bc); USER_PRINTLN("[BusHub75Matrix] "); #else - USER_PRINTLN("[unsupported! BusHub75Matrix] "); + USER_PRINTLN("[unsupported! BusHub75Matrix - add flag -D WLED_ENABLE_HUB75MATRIX] "); return -1; #endif } else if (IS_DIGITAL(bc.type)) { @@ -1003,8 +1281,8 @@ void BusManager::removeAll() { lastend = 0; } -void BusManager::show() { - for (uint8_t i = 0; i < numBusses; i++) { +void __attribute__((hot)) BusManager::show() { + for (unsigned i = 0; i < numBusses; i++) { busses[i]->show(); } } @@ -1073,6 +1351,27 @@ uint32_t IRAM_ATTR __attribute__((hot)) BusManager::getPixelColor(uint_fast16_t return 0; } +uint32_t IRAM_ATTR __attribute__((hot)) BusManager::getPixelColorRestored(uint_fast16_t pix) { // WLEDMM uses bus::getPixelColorRestored() + if ((pix >= laststart) && (pix < lastend ) && (lastBus != nullptr)) { + // WLEDMM same bus as last time - no need to search again + return lastBus->getPixelColorRestored(pix - laststart); + } + + for (uint_fast8_t i = 0; i < numBusses; i++) { + Bus* b = busses[i]; + uint_fast16_t bstart = b->getStart(); + if (pix < bstart || pix >= bstart + b->getLength()) continue; + else { + // WLEDMM remember last Bus we took + lastBus = b; + laststart = bstart; + lastend = bstart + b->getLength(); + return b->getPixelColorRestored(pix - bstart); + } + } + return 0; +} + bool BusManager::canAllShow() const { for (uint8_t i = 0; i < numBusses; i++) { if (!busses[i]->canShow()) return false; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 2379f430..69f3aa2d 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -54,21 +54,25 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; + uint8_t artnet_outputs, artnet_fps_limit; + uint16_t artnet_leds_per_output; + + uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; // WLEDMM warning: this means that BusConfig cannot handle nore than 5 pins per bus! uint16_t frequency; - 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) { + 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 art_o=1, uint16_t art_l=1, uint8_t art_f=30) { 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) count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz; - uint8_t nPins = 1; - if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (type > 47) nPins = 2; - else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); - else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 0; - for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + artnet_outputs = art_o; artnet_leds_per_output = art_l; artnet_fps_limit = art_f; + uint8_t nPins = 1; // default = only one pin (clockless LEDs like WS281x) + if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address + else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins + else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); // PWM needs 1..5 pins + else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 1; // HUB75 does not use LED pins, but we need to preserve the "chain length" parameter + for (uint8_t i = 0; i < min(unsigned(nPins), sizeof(pins)/sizeof(pins[0])); i++) pins[i] = ppins[i]; //softhack007 fix for potential array out-of-bounds access } - //validates start and length and extends total if needed + //validates start and length and extends total if needed // WLEDMM this function is not used anywhere bool adjustBounds(uint16_t& total) { if (!count) count = 1; if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; @@ -135,21 +139,25 @@ class Bus { virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } - virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; }; + virtual uint32_t getPixelColorRestored(uint16_t pix) const { return restore_Color_Lossy(getPixelColor(pix), _bri); } // override in case your bus has a lossless buffer (HUB75, FastLED, Art-Net) + virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; } virtual void cleanup() = 0; virtual uint8_t getPins(uint8_t* pinArray) const { return 0; } - virtual uint16_t getLength() const { return _len; } + virtual inline uint16_t getLength() const { return _len; } virtual void setColorOrder() {} virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() { return 0; } + virtual uint8_t skippedLeds() const { return 0; } virtual uint16_t getFrequency() const { return 0U; } + virtual uint8_t get_artnet_fps_limit() const { return 0; } + virtual uint8_t get_artnet_outputs() const { return 0; } + virtual uint16_t get_artnet_leds_per_output() const { return 0; } inline uint16_t getStart() const { return _start; } inline void setStart(uint16_t start) { _start = start; } inline uint8_t getType() const { return _type; } inline bool isOk() const { return _valid; } inline bool isOffRefreshRequired() const { return _needsRefresh; } - bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start+_len; } - virtual uint16_t getMaxPixels() const { return MAX_LEDS_PER_BUS; }; + //inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start+_len; } // WLEDMM not used, plus wrong - it does not consider skipped pixels + virtual uint16_t getMaxPixels() const { return MAX_LEDS_PER_BUS; } virtual bool hasRGB() const { if ((_type >= TYPE_WS2812_1CH && _type <= TYPE_WS2812_WWA) || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false; @@ -183,6 +191,17 @@ class Bus { inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } inline static uint8_t getGlobalAWMode() { return _gAWM; } + inline static uint32_t restore_Color_Lossy(uint32_t c, uint8_t restoreBri) { // shamelessly grabbed from upstream, who grabbed from NPB, who .. + if (restoreBri < 255) { + uint8_t* chan = (uint8_t*) &c; + for (uint_fast8_t i=0; i<4; i++) { + uint_fast16_t val = chan[i]; + chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale + } + } + return c; + } + bool reversed = false; protected: @@ -207,7 +226,7 @@ class BusDigital : public Bus { inline void show(); - bool canShow() const; + bool canShow() override; void setBrightness(uint8_t b, bool immediate); @@ -215,13 +234,13 @@ class BusDigital : public Bus { void setPixelColor(uint16_t pix, uint32_t c); - uint32_t getPixelColor(uint16_t pix) const; + uint32_t getPixelColor(uint16_t pix) const override; uint8_t getColorOrder() const { return _colorOrder; } - uint16_t getLength() const { + uint16_t getLength() const override { return _len - _skip; } @@ -229,11 +248,11 @@ class BusDigital : public Bus { void setColorOrder(uint8_t colorOrder); - uint8_t skippedLeds() const { + uint8_t skippedLeds() const override { return _skip; } - uint16_t getFrequency() const { return _frequencykHz; } + uint16_t getFrequency() const override { return _frequencykHz; } void reinit(); @@ -267,7 +286,7 @@ class BusPwm : public Bus { uint8_t getPins(uint8_t* pinArray) const; - uint16_t getFrequency() const { return _frequency; } + uint16_t getFrequency() const override { return _frequency; } void cleanup() { deallocatePins(); @@ -296,6 +315,7 @@ class BusOnOff : public Bus { void setPixelColor(uint16_t pix, uint32_t c); uint32_t getPixelColor(uint16_t pix) const; + uint32_t getPixelColorRestored(uint16_t pix) const override { return getPixelColor(pix);} // WLEDMM BusOnOff ignores brightness void show(); @@ -317,7 +337,7 @@ class BusOnOff : public Bus { class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc); + BusNetwork(BusConfig &bc, const ColorOrderMap &com); uint16_t getMaxPixels() const override { return 4096; }; bool hasRGB() const { return true; } @@ -326,20 +346,39 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c); uint32_t __attribute__((pure)) getPixelColor(uint16_t pix) const; // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColorRestored(uint16_t pix) const override { return getPixelColor(pix);} // WLEDMM BusNetwork ignores brightness void show(); - bool canShow() const { + bool canShow() override { // this should be a return value from UDP routine if it is still sending data out return !_broadcastLock; } - uint8_t getPins(uint8_t* pinArray) const; + uint8_t getPins(uint8_t* pinArray) const override; - uint16_t getLength() const { + uint16_t getLength() const override { return _len; } + uint8_t get_artnet_fps_limit() const override { + return _artnet_fps_limit; + } + + uint8_t get_artnet_outputs() const override { + return _artnet_outputs; + } + + uint16_t get_artnet_leds_per_output() const override { + return _artnet_leds_per_output; + } + + void setColorOrder(uint8_t colorOrder); + + uint8_t getColorOrder() const override { + return _colorOrder; + } + void cleanup(); ~BusNetwork() { @@ -347,12 +386,17 @@ class BusNetwork : public Bus { } private: - IPAddress _client; - uint8_t _UDPtype; - uint8_t _UDPchannels; - bool _rgbw; - bool _broadcastLock; - byte *_data; + IPAddress _client; + uint8_t _UDPtype; + uint8_t _UDPchannels; + bool _rgbw; + bool _broadcastLock; + byte *_data; + uint8_t _colorOrder = COL_ORDER_RGB; + uint8_t _artnet_fps_limit; + uint8_t _artnet_outputs; + uint16_t _artnet_leds_per_output; + const ColorOrderMap &_colorOrderMap; }; #ifdef WLED_ENABLE_HUB75MATRIX @@ -360,20 +404,21 @@ class BusHub75Matrix : public Bus { public: BusHub75Matrix(BusConfig &bc); - uint16_t getMaxPixels() const override { return 4096; }; + uint16_t getMaxPixels() const override { return MAX_LEDS; }; bool hasRGB() const override { return true; } bool hasWhite() const override { return false; } void setPixelColor(uint16_t pix, uint32_t c) override; uint32_t getPixelColor(uint16_t pix) const override; + uint32_t getPixelColorRestored(uint16_t pix) const override; // lossless getPixelColor supported void show(void) override; void setBrightness(uint8_t b, bool immediate) override; uint8_t getPins(uint8_t* pinArray) const override { - pinArray[0] = mxconfig.chain_length; + pinArray[0] = activeMXconfig.chain_length; return 1; } // Fake value due to keep finaliseInit happy @@ -386,12 +431,17 @@ class BusHub75Matrix : public Bus { } private: - MatrixPanel_I2S_DMA *display = nullptr; - VirtualMatrixPanel *fourScanPanel = nullptr; - HUB75_I2S_CFG mxconfig; unsigned _panelWidth = 0; CRGB *_ledBuffer = nullptr; byte *_ledsDirty = nullptr; + // C++ dirty trick: private static variables are actually _not_ part of the class (however only visibile to class instances). + // These variables persist when BusHub75Matrix gets deleted. + static MatrixPanel_I2S_DMA *activeDisplay; // active display object + static VirtualMatrixPanel *activeFourScanPanel; // active fourScan object + static HUB75_I2S_CFG activeMXconfig; // last used mxconfig + static uint8_t activeType; // last used type + static uint8_t instanceCount; // active instances - 0 or 1 + static uint8_t last_bri; // last used brightness value (persists on driver delete) }; #endif @@ -418,6 +468,7 @@ class BusManager { void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); uint32_t __attribute__((pure)) getPixelColor(uint_fast16_t pix); // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColorRestored(uint_fast16_t pix); // WLEDMM bool canAllShow() const; @@ -453,4 +504,4 @@ class BusManager { return j; } }; -#endif \ No newline at end of file +#endif diff --git a/wled00/button.cpp b/wled00/button.cpp index c1d03376..fc8cf0f3 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -189,6 +189,7 @@ void handleAnalog(uint8_t b) briLast = bri; bri = 0; } else { + if (bri == 0) strip.restartRuntime(false); bri = aRead; } } else if (macroDoublePress[b] == 249) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 4de49f99..069b303a 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -194,13 +194,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY; + uint8_t artnet_outputs = elm["artnet_outputs"] | 1; // sanity check + uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | length; // sanity check + uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 24; // sanity check if (fromFS) { - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz); + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit); mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { if (busConfigs[s] != nullptr) delete busConfigs[s]; - busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode); + busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit); busesChanged = true; } s++; @@ -829,6 +832,9 @@ void serializeConfig() { ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbwm")] = bus->getAutoWhiteMode(); ins[F("freq")] = bus->getFrequency(); + ins["artnet_outputs"] = bus->get_artnet_outputs(); + ins["artnet_fps_limit"] = bus->get_artnet_fps_limit(); + ins["artnet_leds_per_output"] = bus->get_artnet_leds_per_output(); } JsonArray hw_com = hw.createNestedArray(F("com")); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 23cf2f13..bb12b66a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -8,29 +8,37 @@ * color blend function */ IRAM_ATTR_YN __attribute__((hot)) uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) { - if(blend == 0) return color1; - if (color1 == color2) return color1; // WLEDMM shortcut + if ((color1 == color2) || (blend == 0)) return color1; // WLEDMM const uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF; - if(blend == blendmax) return color2; + if(blend >= blendmax) return color2; const uint_fast8_t shift = b16 ? 16 : 8; - const uint_fast16_t blend2 = blendmax - blend; // WLEDMM pre-calculate value - uint32_t w1 = W(color1); - uint32_t r1 = R(color1); - uint32_t g1 = G(color1); - uint32_t b1 = B(color1); + uint16_t w1 = W(color1); // WLEDMM 16bit to make sure the compiler uses 32bit (not 64bit) for the math + uint16_t r1 = R(color1); + uint16_t g1 = G(color1); + uint16_t b1 = B(color1); - uint32_t w2 = W(color2); - uint32_t r2 = R(color2); - uint32_t g2 = G(color2); - uint32_t b2 = B(color2); + uint16_t w2 = W(color2); + uint16_t r2 = R(color2); + uint16_t g2 = G(color2); + uint16_t b2 = B(color2); - uint32_t w3 = ((w2 * blend) + (w1 * blend2)) >> shift; - uint32_t r3 = ((r2 * blend) + (r1 * blend2)) >> shift; - uint32_t g3 = ((g2 * blend) + (g1 * blend2)) >> shift; - uint32_t b3 = ((b2 * blend) + (b1 * blend2)) >> shift; - - return RGBW32(r3, g3, b3, w3); + if (b16 == false) { + // WLEDMM based on fastled blend8() - better accuracy for 8bit + uint8_t w3 = (w1+w2 == 0) ? 0 : (((w1 << 8)|w2) + (w2 * blend) - (w1*blend) ) >> 8; + uint8_t r3 = (((r1 << 8)|r2) + (r2 * blend) - (r1*blend) ) >> 8; + uint8_t g3 = (((g1 << 8)|g2) + (g2 * blend) - (g1*blend) ) >> 8; + uint8_t b3 = (((b1 << 8)|b2) + (b2 * blend) - (b1*blend) ) >> 8; + return RGBW32(r3, g3, b3, w3); + } else { + // old code has lots of "jumps" due to roundding errors + const uint_fast16_t blend2 = blendmax - blend; // WLEDMM pre-calculate value + uint32_t w3 = ((w2 * blend) + (w1 * blend2)) >> shift; + uint32_t r3 = ((r2 * blend) + (r1 * blend2)) >> shift; + uint32_t g3 = ((g2 * blend) + (g1 * blend2)) >> shift; + uint32_t b3 = ((b2 * blend) + (b1 * blend2)) >> shift; + return RGBW32(r3, g3, b3, w3); + } } /* @@ -73,27 +81,30 @@ IRAM_ATTR_YN uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) // WLEDMM IRAM_ATTR_YN __attribute__((hot)) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { + if (amount == 255) return c1; // WLEDMM small optimization - plus it avoids over-fading in "video" mode if (amount == 0) return 0; // WLEDMM shortcut - uint32_t scaledcolor; // color order is: W R G B from MSB to LSB - uint32_t r = R(c1); - uint32_t g = G(c1); - uint32_t b = B(c1); - uint32_t w = W(c1); + uint32_t scaledcolor = 0; // color order is: W R G B from MSB to LSB + uint16_t w = W(c1); // WLEDMM 16bit to make sure the compiler uses 32bit (not 64bit) for the math + uint16_t r = R(c1); + uint16_t g = G(c1); + uint16_t b = B(c1); if (video) { - uint32_t scale = amount; // 32bit for faster calculation - scaledcolor = (((r * scale) >> 8) << 16) + ((r && scale) ? 1 : 0); - scaledcolor |= (((g * scale) >> 8) << 8) + ((g && scale) ? 1 : 0); - scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); - if (w>0) scaledcolor |= (((w * scale) >> 8) << 24) + ((scale) ? 1 : 0); // WLEDMM small speedup when no white channel + uint16_t scale = amount; // 32bit for faster calculation + // bugfix: doing "+1" after shifting is obviously wrong + // optimization: ((r && scale) ? 1 : 0) can be simplified to "if (r > 0) +1" ; if we arive here, then scale != 0 and scale < 255 + if (w>0) scaledcolor |= (((w * scale) >> 8) +1) << 24; // WLEDMM small speedup when no white channel + if (r>0) scaledcolor |= (((r * scale) >> 8) +1) << 16; + if (g>0) scaledcolor |= (((g * scale) >> 8) +1) << 8; + if (b>0) scaledcolor |= ((b * scale) >> 8) +1; return scaledcolor; } else { - uint32_t scale = 1 + amount; - scaledcolor = ((r * scale) >> 8) << 16; - scaledcolor |= ((g * scale) >> 8) << 8; - scaledcolor |= (b * scale) >> 8; + uint16_t scale = 1 + amount; if (w>0) scaledcolor |= ((w * scale) >> 8) << 24; // WLEDMM small speedup when no white channel + scaledcolor |= ((r * scale) >> 8) << 16; + scaledcolor |= (g * scale) & 0x0000FF00; // WLEDMM faster than right-left shift "" >>8 ) <<8" + scaledcolor |= (b * scale) >> 8; return scaledcolor; } } diff --git a/wled00/const.h b/wled00/const.h index ff8b0371..2c6ac1a7 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -245,18 +245,23 @@ #define TYPE_P9813 53 #define TYPE_LPD6803 54 +// WLEDMM additional types #define TYPE_HUB75MATRIX 100 // 100 - 110 +// WLEDMM caution - do not use bus types > 127 //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) -#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused) +#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus) +#define TYPE_NET_ARTNET_RGBW 83 //network ArtNet RGB bus (master broadcast bus) #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define IS_DIGITAL(t) (((t) & 0x10) || ((t)==TYPE_HUB75MATRIX)) //digital are 16-31 and 48-63 // WLEDMM added HUB75 #define IS_PWM(t) ((t) > 40 && (t) < 46) #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_2PIN(t) ((t) > 47) +#define IS_VIRTUAL(t) ( ((t) <= TYPE_RESERVED) || (((t) >= TYPE_NET_DDP_RGB) && ((t) < (TYPE_NET_DDP_RGB + 16))) ) // WLEDMM 80..95 are network "virtual" busses +#define EXCLUDE_FROM_ABL(t) ( IS_VIRTUAL(t) || ( (t) >= (TYPE_HUB75MATRIX) && (t) < (TYPE_HUB75MATRIX + 10))) // WLEDMM do not apply auto-brightness-limiter on these bus types //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -353,6 +358,10 @@ #define ERR_LOW_WS_MEM 35 // WLEDMM: low memory (ws) #define ERR_LOW_AJAX_MEM 36 // WLEDMM: low memory (oappend) #define ERR_LOW_BUF 37 // WLEDMM: low memory (LED buffer from allocLEDs) +#define ERR_SYS_REBOOT 90 // WLEDMM: reboot after error +#define ERR_SYS_BROWNOUT 91 // WLEDMM: reboot after brownout alert +#define ERR_REBOOT_NEEDED 98 // WLEDMM: reboot needed after changing hardware setting +#define ERR_POWEROFF_NEEDED 99 // WLEDMM: power-cycle needed after changing hardware setting // Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness @@ -387,7 +396,11 @@ #define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs #else //#define MAX_LEDS 8192 -#define MAX_LEDS 8464 // WLEDMM 92x92 +#if !defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_LEDS 8464 // WLEDMM 92x92 for esp32, esp32-S2 and esp32-c3 +#else + #define MAX_LEDS 18436 // WLEDMM 128x128 + 2048 + 4 for esp32-S3 +#endif #endif #endif diff --git a/wled00/data/index.css b/wled00/data/index.css index d8338d93..617f971b 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -352,7 +352,7 @@ button { #putil, #segutil, #segutil2 { min-height: 42px; - margin: 13px auto 0; + margin: 0 auto; } #segutil .segin { @@ -1041,7 +1041,7 @@ textarea { .segname .flr, .pname .flr { transform: rotate(0deg); - right: -6px; + right: 4px; } /* segment power wrapper */ @@ -1331,6 +1331,11 @@ TD .checkmark, TD .radiomark { box-shadow: 0px 0px 10px 4px var(--c-1); } +.lstI .flr:hover { + background: var(--c-6); + border-radius: 100%; +} + #pcont .selected:not([class*="expanded"]) { bottom: 52px; top: 42px; diff --git a/wled00/data/index.js b/wled00/data/index.js index 05aaee05..7dced6a4 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -717,7 +717,7 @@ ${i.psram?inforow("PSRAM ☾",((i.tpram-i.psram)/1024).toFixed(0)+"/"+(i.tpram/1 ${i.psusedram?inforow("Max used PSRAM ☾",((i.tpram-i.psusedram)/1024).toFixed(0)+" kB",", "+((i.tpram-i.psusedram)*100.0/i.tpram).toFixed(1)+"%"):""} ${i.freestack?inforow("Free stack ☾",(i.freestack/1000).toFixed(3)," kB"):""}
-${i.tpram?inforow("PSRAM " + (i.psrmode?"("+i.psrmode+" mode) ":"") + " ☾",(i.tpram/1024/1024).toFixed(0)," MB"):""} +${i.tpram?inforow("PSRAM " + (i.psrmode?"("+i.psrmode+" mode) ":"") + " ☾",(i.tpram/1024/1024).toFixed(0)," MB"):inforow("NO PSRAM found.", "")} ${i.e32flash?inforow("Flash mode "+i.e32flashmode+i.e32flashtext + " ☾",i.e32flash+" MB, "+i.e32flashspeed," Mhz"):""} ${i.e32model?inforow(i.e32model + " ☾",i.e32cores +" core(s),"," "+i.e32speed+" Mhz"):""} ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} @@ -2011,8 +2011,20 @@ function readState(s,command=false) case 37: errstr = "no memory for LEDs buffer."; break; + case 90: + errstr = "Unexpected Restart. Check serial monitor."; + break; + case 91: + errstr = "Brownout Restart."; + break; + case 98: + errstr = "Please reboot WLED to activate changed settings."; + break; + case 99: + errstr = "Please switch your device off and back on."; + break; } - showToast('Error ' + s.error + ": " + errstr, true); + showToast(((s.error < 33)?'Error ':'Warning ') + s.error + ": " + errstr, (s.error < 35)||(s.error > 90)); } selectedPal = i.pal; @@ -2357,7 +2369,7 @@ function makeSeg() }); var cn = `
`+ `
`+ - ``+ + ``+ ``+ ``+ ``+ @@ -2383,7 +2395,7 @@ function makeSeg() function resetUtil(off=false) { - gId('segutil').innerHTML = `
` + gId('segutil').innerHTML = `
` + '' + `
Add segment
` + '
' diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index c602b98c..85f9dddb 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -98,7 +98,7 @@ var pw = parseInt(d.Sf.PW.value); var ph = parseInt(d.Sf.PH.value); //WLEDMM: change name to id - let b = `

Panel ${i}
+ let b = `

Panel ${i+1}
1st LED:
Serpentine:
-Dimensions (WxH): x
-Offset X: -Y:
(offset from top-left corner in # LEDs) +Dimensions (WxH): x
+Offset X: +Y:
(offset from top-left corner in # LEDs)
`; p.insertAdjacentHTML("beforeend", b); } @@ -334,7 +334,7 @@ Y:Matrix Generator
${isM?'Start X':'Start LED'}