diff --git a/docs/dev.md b/docs/dev.md index ba420bd5..6c76444f 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -35,6 +35,7 @@ The JSON object has the following properties: | `stats_interval` | integer | Sets the interval in milliseconds when awtrix should send its stats to HA and MQTT | 10000 | | `debug_mode` | boolean | Enables serial debug outputs. | false | | `dfplayer` | boolean | Enables DFPLayer for Awtrix2_conversation builds. | false | +| `buzzer_volume` | boolean | Activates the volume control for the buzzer, doesnt work with every tones | false | | `button_callback` | string | http callback url for button presses. | - | | `new_year` | boolean | Displays fireworks and plays a jingle at newyear. | false | diff --git a/docs/hardware.md b/docs/hardware.md index c5de21b7..a59cfdfa 100644 --- a/docs/hardware.md +++ b/docs/hardware.md @@ -5,16 +5,21 @@ if you want to build your own AWTRIX 3, here are the pinout of the Ulanzi clock |ESP32 PIN | GPIO | Usage or part | |---|-------|-----------------------------------------------| -|6| ADC6 / GPIO34 | Battery sensor | -|7 | ADC7 / GPIO35 | LDR (light) sensor (GL5516) | -|8| GPIO32 | Matrix | -|11| GPIO26 | Left button | -|12| GPIO27 | Middle button | -|13| GPIO14 | Right button | -|23| GPIO15 | Buzzer | -|33/36| 21/22 | Temperature and Humidity Sensors (SHT3x) | - - +|6 | ADC6 / GPIO34 | Battery sensor (optional) | +|7 | ADC7 / GPIO35 | LDR (light) sensor (GL5516) (optional) | +|8 | GPIO32 | Matrix | +|11 | GPIO26 | Left button (optional) | +|12 | GPIO27 | Middle button (optional) | +|13 | GPIO14 | Right button (optional) | +|23 | GPIO15 | Buzzer (optional) | +|33/36| 21 (SCL) /22(SDA) | Temperature and Humidity Sensors (optional) | +|37/30| 23(RX)/18(TX) | DFplayer (optional) | + +Supported Sensors: +BME280 +BMP280 +HTU21df +SHT31 If the matrix displays meaningless characters, the matrix type must be changed. diff --git a/lib/Melody Player/.clang-format b/lib/Melody Player/.clang-format deleted file mode 100644 index 849b7608..00000000 --- a/lib/Melody Player/.clang-format +++ /dev/null @@ -1,154 +0,0 @@ -# Config file found in Arduino Language Server (https://github.com/arduino/arduino-language-server/blob/0.6.0/ls/ls_formatter.go) -# CHANGES: -# - ReflowComments: true -# - MaxEmptyLinesToKeep: 1 -# - ColumnLimit: 100 -# - BreakStringLiterals: true -# - AlignConsecutiveMacros: AcrossEmptyLines -# - AlignArrayOfStructures: Left - -Language: Cpp -# LLVM is the default style setting, used when a configuration option is not set here -BasedOnStyle: LLVM -AccessModifierOffset: -2 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveBitFields: false -AlignConsecutiveDeclarations: false -AlignConsecutiveMacros: false -AlignEscapedNewlines: DontAlign -AlignOperands: Align -AlignTrailingComments: true -AllowAllArgumentsOnNextLine: true -AllowAllConstructorInitializersOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: Always -AllowShortCaseLabelsOnASingleLine: true -AllowShortEnumsOnASingleLine: true -AllowShortFunctionsOnASingleLine: Empty -AllowShortIfStatementsOnASingleLine: Always -AllowShortLambdasOnASingleLine: Empty -AllowShortLoopsOnASingleLine: true -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: No -BinPackArguments: true -BinPackParameters: true -# Only used when "BreakBeforeBraces" set to "Custom" -BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: Never - AfterEnum: false - AfterFunction: false - AfterNamespace: false - #AfterObjCDeclaration: - AfterStruct: false - AfterUnion: false - AfterExternBlock: false - BeforeCatch: false - BeforeElse: false - BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: false - SplitEmptyNamespace: false -# Java-specific -#BreakAfterJavaFieldAnnotations: -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Attach -BreakBeforeTernaryOperators: true -BreakConstructorInitializers: BeforeColon -BreakInheritanceList: BeforeColon -BreakStringLiterals: true -ColumnLimit: 100 -# "" matches none -CommentPragmas: "" -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 2 -ContinuationIndentWidth: 2 -Cpp11BracedListStyle: false -DeriveLineEnding: true -DerivePointerAlignment: true -DisableFormat: false -# Docs say "Do not use this in config files". The default (LLVM 11.0.1) is "false". -#ExperimentalAutoDetectBinPacking: -FixNamespaceComments: false -ForEachMacros: [] -IncludeBlocks: Preserve -IncludeCategories: [] -# "" matches none -IncludeIsMainRegex: "" -IncludeIsMainSourceRegex: "" -IndentCaseBlocks: true -IndentCaseLabels: true -IndentExternBlock: Indent -IndentGotoLabels: false -IndentPPDirectives: None -IndentWidth: 2 -IndentWrappedFunctionNames: false -InsertTrailingCommas: None -# Java-specific -#JavaImportGroups: -# JavaScript-specific -#JavaScriptQuotes: -#JavaScriptWrapImports -KeepEmptyLinesAtTheStartOfBlocks: true -MacroBlockBegin: "" -MacroBlockEnd: "" -# Set to a large number to effectively disable -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -NamespaceMacros: [] -# Objective C-specific -#ObjCBinPackProtocolList: -#ObjCBlockIndentWidth: -#ObjCBreakBeforeNestedBlockParam: -#ObjCSpaceAfterProperty: -#ObjCSpaceBeforeProtocolList -PenaltyBreakAssignment: 1 -PenaltyBreakBeforeFirstCallParameter: 1 -PenaltyBreakComment: 1 -PenaltyBreakFirstLessLess: 1 -PenaltyBreakString: 1 -PenaltyBreakTemplateDeclaration: 1 -PenaltyExcessCharacter: 1 -PenaltyReturnTypeOnItsOwnLine: 1 -# Used as a fallback if alignment style can't be detected from code (DerivePointerAlignment: true) -PointerAlignment: Right -RawStringFormats: [] -ReflowComments: true -SortIncludes: false -SortUsingDeclarations: false -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceBeforeRangeBasedForLoopColon: true -SpaceBeforeSquareBrackets: false -SpaceInEmptyBlock: false -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -SpacesInConditionalStatement: false -SpacesInContainerLiterals: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Auto -StatementMacros: [] -TabWidth: 2 -TypenameMacros: [] -# Default to LF if line endings can't be detected from the content (DeriveLineEnding). -UseCRLF: false -UseTab: Never -WhitespaceSensitiveMacros: [] -AlignConsecutiveMacros: AcrossEmptyLines -AlignArrayOfStructures: Left diff --git a/lib/Melody Player/.gitignore b/lib/Melody Player/.gitignore deleted file mode 100644 index 57621422..00000000 --- a/lib/Melody Player/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch diff --git a/lib/Melody Player/.piopm b/lib/Melody Player/.piopm deleted file mode 100644 index b1d4c839..00000000 --- a/lib/Melody Player/.piopm +++ /dev/null @@ -1 +0,0 @@ -{"type": "library", "name": "Melody Player", "version": "2.4.0", "spec": {"owner": "fabianoriccardi", "id": 7443, "name": "Melody Player", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/Melody Player/LICENSE b/lib/Melody Player/LICENSE deleted file mode 100644 index bc0390e9..00000000 --- a/lib/Melody Player/LICENSE +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -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 this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -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 -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser 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 Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "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 -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY 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 -LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey 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 library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random - Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/lib/Melody Player/README.md b/lib/Melody Player/README.md deleted file mode 100644 index 9f959b3c..00000000 --- a/lib/Melody Player/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Melody Player - -[![arduino-library-badge](https://www.ardu-badge.com/badge/Melody%20Player.svg)](https://www.ardu-badge.com/Melody%20Player) ![Compile Library Examples](https://github.com/fabianoriccardi/melody-player/actions/workflows/LibraryBuild.yml/badge.svg) - -Melody Player is an Arduino library to play melodies on buzzers on ESP8266 and ESP32 in a non-blocking manner. Melodies can be written directly in code or loaded from file. It supports Ring Tone Text Transfer Language (RTTTL) format and a custom format developed specifically to enjoy all the benefits of this library. - -## Motivation - -Arduino cores provide tone() function to emit a PWM signal, and it is often used to play beeps on a buzzer. Modulating the PWM frequency, you can play sounds at a given frequency, i.e., you can play a note, and it is relatively easy to play a monophonic melody. However, the Arduino ecosystem lacks of a structured and easy way to accomplish this task (i.e., each developer have to write bloating code for it). So, I started to write a simple snippet to asynchronously play sequences of notes on a buzzer without bloating user-code with long parsing and playback methods. Moreover, since ESP8266 and ESP32 cores provide a file system for the embedded flash melody, I wanted a melody format easy to remember, human-readable and editable with a simple text editor. From this context Melody Player was born, improved, and extended over time. - -## Features - -* Non-blocking playback -* Support RTTTL format (allow reuse of ringtones popular on old mobile phones) -* Support custom format to allow finer control of frequencies -* For ESP8266 and ESP32 -* Load melodies from file (support both LittleFS and SPIFFS file systems) -* Control the melody playback through trivial *play*, *pause*, *stop* methods -* Support multiple playing buzzers -* *Migration* and *duplication* of melodies among buzzers - -## Installation - -You can find Melody Player on Arduino and PlatformIO library registries. You can install it through Arduino IDE, or you can use the respective command-line tools running: - - arduino-cli lib install "Melody Player" - -or: - - pio lib install "fabianoriccardi/Melody Player" - -## Usage - -Here a quick overview of the main methods to use this library. Initialize MelodyPlayer by specifying the pin where the buzzer is attached to: - - MelodyPlayer player(4); - -Load the RTTTL melody from file (remember to initialize the file system before this call): - - Melody melody = MelodyFactory.loadRtttlFile("/the-anthem.rtttl"); - -Check if the melody is loaded correctly: - - if(!melody) { - Serial.println("Cannot play this melody"); - } - -Play it using blocking or non-blocking methods: - - player.play(melody); - -or - - player.playAsync(melody); - -In case of non-blocking playback, you can check if the melody is running: - - if(player.isPlaying()){ - Serial.println("Playing..."); - } - -and pause/continue to play/stop the melody through: - - player.pause(); - player.play(); - player.stop(); - -## Details about the custom format - -You can write a melody in a text file, accordingly to the following specifications: - - title={Name of the melody} - timeUnit={Base time in millisecond} - length={Array length} - format={This value can be "integer" or "string", and it specifies how the tone frequency is represented in the following array} - {Array composed by pair and spit by '|' (pipe character)} - -where: - -* *frequency* can be either an integer number or a string, depending on what specified in "format" parameter. If format type is "string", it represents a note in English convention e.g. E5, F1 -* *duration* is an integer expressed in "timeUnits" - -A small pause is automatically added between 2 consecutive pairs. You can add comments using '#' at the begin of the line. - -Example 1: this melody codifies 2 "beeps" using the "string" codification: - - title=Beep - timeUnit=200 - length=1 - format=string - G7,3|SILENCE,1|G7,3 - -Example 2: the same melody using the "integer" codification: - - title=Beep - timeUnit=200 - length=1 - format=integer - 3136,3|0,1|3136,3 - -## Useful links - -* : RTTTL specification -* : Test and listen RTTTL melodies diff --git a/lib/Melody Player/keywords.txt b/lib/Melody Player/keywords.txt deleted file mode 100644 index 96e5ff6a..00000000 --- a/lib/Melody Player/keywords.txt +++ /dev/null @@ -1,20 +0,0 @@ -Melody KEYWORD1 -MelodyPlayer KEYWORD1 -MelodyFactory KEYWORD1 - -play KEYWORD2 -playAsync KEYWORD2 -isPlaying KEYWORD2 -stop KEYWORD2 -pause KEYWORD2 -transferMelodyTo KEYWORD2 -duplicateMelodyTo KEYWORD2 - -getTitle KEYWORK2 -getTimeUnit KEYWORD2 -getLength KEYWORD2 -getNote KEYWORD2 -getAutomaticSilence KEYWORD2 -isValid KEYWORD2 - -load KEYWORD2 diff --git a/lib/Melody Player/library.json b/lib/Melody Player/library.json deleted file mode 100644 index d0638585..00000000 --- a/lib/Melody Player/library.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "name": "Melody Player", - "version": "2.4.0", - "authors": { - "name": "Fabiano Riccardi", - "email": "fabiano.riccardi@outlook.com" - }, - "description": "This library provides an intuitive interface to play melodies on buzzers. The melodies can be stored in file systems (SPIFFS or LittleFS) or hardcoded in your sketch. Support RTTTL.", - "keywords": [ - "buzzer", - "tune", - "melody", - "rtttl", - "piezo", - "arduino-library", - "sound" - ], - "repository": { - "type": "git", - "url": "https://github.com/fabianoriccardi/melody-player" - }, - "licence": "LGPL-2.1-or-later", - "frameworks": [ - "arduino" - ], - "platforms": [ - "espressif8266", - "espressif32" - ], - "examples": [ - { - "name": "1_simple_play", - "base": "examples/1_simple_play", - "files": [ - "1_simple_play.ino" - ] - }, - { - "name": "2_control_melody", - "base": "examples/2_control_melody", - "files": [ - "2_control_melody.ino" - ] - }, - { - "name": "3_load_melody_from_file", - "base": "examples/3_load_melody_from_file", - "files": [ - "3_load_melody_from_file.ino" - ] - }, - { - "name": "4_load_rtttl_melody", - "base": "examples/4_load_rtttl_melody", - "files": [ - "4_load_rtttl_melody.ino" - ] - }, - { - "name": "5_play_melodies_simultaneously", - "base": "examples/5_play_melodies_simultaneously", - "files": [ - "5_play_melodies_simultaneously.ino" - ] - }, - { - "name": "6_transfer_playing_melody", - "base": "examples/6_transfer_playing_melody", - "files": [ - "6_transfer_playing_melody.ino" - ] - }, - { - "name": "7_duplicate_playing_melody", - "base": "examples/7_duplicate_playing_melody", - "files": [ - "7_duplicate_playing_melody.ino" - ] - } - ] -} \ No newline at end of file diff --git a/lib/Melody Player/library.properties b/lib/Melody Player/library.properties deleted file mode 100644 index 62df5e94..00000000 --- a/lib/Melody Player/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=Melody Player -version=2.4.0 -author=Fabiano Riccardi -maintainer=Fabiano Riccardi -sentence=This library provides an intuitive interface to play melodies on buzzers -paragraph=The melodies can be stored in file systems (SPIFFS or LittleFS) or hardcoded in your sketch. Support RTTTL. -category=Device Control -url=https://github.com/fabianoriccardi/melody-player -architectures=esp8266,esp32 diff --git a/lib/Melody Player/platformio.ini b/lib/Melody Player/platformio.ini deleted file mode 100644 index 45b5ae36..00000000 --- a/lib/Melody Player/platformio.ini +++ /dev/null @@ -1,28 +0,0 @@ -[env] -monitor_speed = 115200 -upload_speed = 921600 -#upload_port = COM6 -build_flags = -Wall -Wextra -#board_build.filesystem = littlefs - -[platformio] -# Uncomment the example to need to compile -src_dir = examples/1_simple_play -#src_dir = examples/2_control_melody -#src_dir = examples/3_load_melody_from_file -#src_dir = examples/4_load_rtttl_melody -#src_dir = examples/5_play_melodies_simultaneously -#src_dir = examples/6_transfer_playing_melody -#src_dir = examples/7_duplicate_playing_melody -lib_dir = . -#data_dir=examples/4_load_rtttl_melody/data - -[env:esp8266] -platform = espressif8266 -board = d1_mini -framework = arduino - -[env:esp32] -platform = espressif32 -board = lolin32 -framework = arduino diff --git a/src/AwtrixFont.h b/src/AwtrixFont.h index eef88354..7c2f85cf 100644 --- a/src/AwtrixFont.h +++ b/src/AwtrixFont.h @@ -185,7 +185,7 @@ const uint8_t AwtrixBitmaps[] PROGMEM = { 0xB8, 0xA8, 0xE8, 0xA8, 0xB8, /*[125] 0x9D Ю */ 0xE0, 0xA0, 0x60, 0xA0, 0xA0, /*[126] 0x9E Я */ 0x20, 0xE0, 0x80, 0x80, 0x80, 0x00, 0x00, /*[127] 0x9F Ґ */ - 0x60, 0x80, 0xC0, 0x80, 0x60, /*[128] 0xA0 Є */ + 0x60, 0x80, 0xC0, 0x80, 0x60, /*[128] 0xA0 Є */ 0x80, 0x00, 0x80, 0x80, 0x80, /*[129] 0xA1 exclamdown */ 0x40, 0xE0, 0x80, 0xE0, 0x40, /*[130] 0xA2 cent */ @@ -208,7 +208,8 @@ const uint8_t AwtrixBitmaps[] PROGMEM = { 0xE0, 0x60, 0xE0, /*[147] 0xB3 threesuperior */ 0x40, 0x80, /*[148] 0xB4 acute */ 0xA0, 0xA0, 0xA0, 0xC0, 0x80, /*[149] 0xB5 mu */ - 0x60, 0xA0, 0x60, 0x60, 0x60, /*[150] 0xB6 paragraph */ + // 0x60, 0xA0, 0x60, 0x60, 0x60, /*[150] 0xB6 paragraph */ + 0x60, 0xC0, 0xE0, 0xC0, 0x60, /*[150] 0x20AC Euro */ 0xE0, 0xE0, 0xE0, /*[151] 0xB7 periodcentered */ 0x40, 0x20, 0xC0, /*[152] 0xB8 cedilla */ 0x80, 0x80, 0x80, /*[153] 0xB9 onesuperior */ @@ -282,20 +283,6 @@ const uint8_t AwtrixBitmaps[] PROGMEM = { 0x20, 0x40, 0xA0, 0x60, 0x20, 0x40, /*[221] 0xFD yacute */ 0x80, 0xC0, 0xA0, 0xC0, 0x80, /*[222] 0xFE thorn */ 0xA0, 0x00, 0xA0, 0x60, 0x20, 0x40, /*[223] 0xFF ydieresis */ - 0x00, /*[224] 0x11D gcircumflex */ - 0x60, 0xC0, 0xE0, 0xC0, 0x60, /*[225] 0x152 OE */ - 0x60, 0xE0, 0xC0, 0xE0, /*[226] 0x153 oe */ - 0xA0, 0x60, 0xC0, 0x60, 0xC0, /*[227] 0x160 Scaron */ - 0xA0, 0x60, 0xC0, 0x60, 0xC0, /*[228] 0x161 scaron */ - 0xA0, 0x00, 0xA0, 0x40, 0x40, /*[229] 0x178 Ydieresis */ - 0xA0, 0xE0, 0x60, 0xC0, 0xE0, /*[230] 0x17D Zcaron */ - 0xA0, 0xE0, 0x60, 0xC0, 0xE0, /*[231] 0x17E zcaron */ - 0x00, /*[232] 0xEA4 uni0EA4 */ - 0x00, /*[233] 0x13A0 uni13A0 */ - 0x80, /*[234] 0x2022 bullet */ - 0xA0, /*[235] 0x2026 ellipsis */ - 0x60, 0xC0, 0xE0, 0xC0, 0x60, /*[236] 0x20AC Euro */ - 0xE0, 0xA0, 0xA0, 0xA0, 0xE0, /*[237] 0xFFFD uniFFFD */ }; /* {offset, width, height, advance cursor, x offset, y offset} */ @@ -526,20 +513,6 @@ const GFXglyph AwtrixFontGlyphs[] PROGMEM = { {1010 , 8 , 6 , 4 , 0 , -5 }, /*[221] 0xFD yacute */ {1016 , 8 , 5 , 4 , 0 , -4 }, /*[222] 0xFE thorn */ {1021 , 8 , 6 , 4 , 0 , -5 }, /*[223] 0xFF ydieresis */ - {1027 , 8 , 1 , 2 , 0 , -1 }, /*[224] 0x11D gcircumflex */ - {1028 , 8 , 5 , 4 , 0 , -5 }, /*[225] 0x152 OE */ - {1033 , 8 , 4 , 4 , 0 , -4 }, /*[226] 0x153 oe */ - {1037 , 8 , 5 , 4 , 0 , -5 }, /*[227] 0x160 Scaron */ - {1042 , 8 , 5 , 4 , 0 , -5 }, /*[228] 0x161 scaron */ - {1047 , 8 , 5 , 4 , 0 , -5 }, /*[229] 0x178 Ydieresis */ - {1052 , 8 , 5 , 4 , 0 , -5 }, /*[230] 0x17D Zcaron */ - {1057 , 8 , 5 , 4 , 0 , -5 }, /*[231] 0x17E zcaron */ - {1062 , 8 , 1 , 2 , 0 , -1 }, /*[232] 0xEA4 uni0EA4 */ - {1063 , 8 , 1 , 2 , 0 , -1 }, /*[233] 0x13A0 uni13A0 */ - {1064 , 8 , 1 , 2 , 0 , -3 }, /*[234] 0x2022 bullet */ - {1065 , 8 , 1 , 4 , 0 , -1 }, /*[235] 0x2026 ellipsis */ - {1066 , 8 , 5 , 4 , 0 , -5 }, /*[236] 0x20AC Euro */ - {1071 , 8 , 5 , 4 , 0 , -5 }, /*[237] 0xFFFD uniFFFD */ }; const GFXfont AwtrixFont PROGMEM = { diff --git a/src/Functions.cpp b/src/Functions.cpp index 011fb41e..cd18df83 100644 --- a/src/Functions.cpp +++ b/src/Functions.cpp @@ -126,10 +126,10 @@ float getTextWidth(const char *text, byte textCase) { width += 7; } - else if (current_char == 0x99 || - current_char == 0x95 || - current_char == 0x87 || - current_char == 0x88) + else if (current_char == 0x99 || + current_char == 0x95 || + current_char == 0x87 || + current_char == 0x88) { width += 5; } @@ -168,7 +168,8 @@ byte utf8ascii(byte ascii) case 0x82: if (ascii == 0xAC) - return (0xEA); + // return (0xEA); + return (0xB6); case 0xD0: if (ascii == 0x81) // Ё @@ -205,11 +206,10 @@ byte utf8ascii(byte ascii) if (ascii == 0x91) // ґ return 0x9F; - } return (0); } - + String utf8ascii(String s) { String r = ""; diff --git a/src/Globals.cpp b/src/Globals.cpp index 87759188..7c60da84 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -161,6 +161,11 @@ void loadDevSettings() HOSTNAME = doc["hostname"].as(); } + if (doc.containsKey("buzzer_volume")) + { + BUZ_VOL = doc["buzzer_volume"].as(); + } + if (doc.containsKey("web_port")) { WEB_PORT = doc["web_port"]; @@ -333,7 +338,7 @@ IPAddress gateway; IPAddress subnet; IPAddress primaryDNS; IPAddress secondaryDNS; -const char *VERSION = "0.95"; +const char *VERSION = "0.96"; String MQTT_HOST = ""; uint16_t MQTT_PORT = 1883; @@ -446,4 +451,5 @@ bool GAME_ACTIVE = false; uint32_t AP_TIMEOUT = 15; int WEB_PORT = 80; OverlayEffect GLOBAL_OVERLAY = NONE; -String HOSTNAME = ""; \ No newline at end of file +String HOSTNAME = ""; +bool BUZ_VOL = false; \ No newline at end of file diff --git a/src/Globals.h b/src/Globals.h index 6fdff234..073d3a84 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -27,6 +27,7 @@ #define DEBUG_PRINTLN(x) #define DEBUG_PRINTF(format, ...) #endif + void formatSettings(); extern const char *uniqueID; extern const char *VERSION; @@ -146,4 +147,5 @@ extern uint32_t AP_TIMEOUT; extern OverlayEffect GLOBAL_OVERLAY; extern String HOSTNAME; extern int WEB_PORT; +extern bool BUZ_VOL; #endif // Globals_H \ No newline at end of file diff --git a/lib/Melody Player/src/melody.h b/src/MelodyPlayer/melody.h similarity index 96% rename from lib/Melody Player/src/melody.h rename to src/MelodyPlayer/melody.h index 8e7a2065..bac22ccb 100644 --- a/lib/Melody Player/src/melody.h +++ b/src/MelodyPlayer/melody.h @@ -1,114 +1,114 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#ifndef MELODY_H -#define MELODY_H - -#include - -#include -#include - -/** - * A note and its duration. - */ -struct NoteDuration { - // The note frequency. - unsigned short frequency; - // The note duration. The representation can be either relative (fixed-point, decimal - // part = 1 bit) to time units or absolute (in milliseconds) - unsigned short duration; -}; - -/** - * This class stores the data melody (notes and metadata). - * To ease the creation of a melody, you should use MelodyFactory class. - */ -class Melody { -public: - Melody() : notes(nullptr){}; - - Melody(String title, unsigned short timeUnit, std::shared_ptr> notes, bool automaticSilence) - : title(title), timeUnit(timeUnit), notes(notes), automaticSilence(automaticSilence){}; - - /** - * Return the title of the melody. - */ - String getTitle() const { - return title; - }; - - /** - * Return the time unit (i.e. the minimum length of a note), in milliseconds. - */ - unsigned short getTimeUnit() const { - return timeUnit; - }; - - /** - * Get the number of notes. - */ - unsigned short getLength() const { - if (notes == nullptr) return 0; - return (*notes).size(); - } - - /** - * Get the note at the given position. - * If the melody or the position is invalid, return a zeroed NoteDuration. - */ - NoteDuration getNote(unsigned short i) const { - if (i < getLength()) { return (*notes)[i]; } - return { 0, 0 }; - } - - /** - * Return true if the melody should be played with a small delay between each note. - */ - bool getAutomaticSilence() const { - return automaticSilence; - } - - /** - * Return true if the melody is valid, false otherwise. - */ - bool isValid() const { - return notes != nullptr && (*notes).size() != 0; - } - - /** - * Return true if the melody is valid, false otherwise. - */ - explicit operator bool() const { - return isValid(); - } - -private: - String title; - // in milliseconds - unsigned short timeUnit; - std::shared_ptr> notes; - const static unsigned short maxLength = 1000; - bool automaticSilence; - - // Enable debug messages over serial port - const static bool debug = false; -}; - -#endif // END MELODY_H +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#ifndef MELODY_H +#define MELODY_H + +#include + +#include +#include + +/** + * A note and its duration. + */ +struct NoteDuration { + // The note frequency. + unsigned short frequency; + // The note duration. The representation can be either relative (fixed-point, decimal + // part = 1 bit) to time units or absolute (in milliseconds) + unsigned short duration; +}; + +/** + * This class stores the data melody (notes and metadata). + * To ease the creation of a melody, you should use MelodyFactory class. + */ +class Melody { +public: + Melody() : notes(nullptr){}; + + Melody(String title, unsigned short timeUnit, std::shared_ptr> notes, bool automaticSilence) + : title(title), timeUnit(timeUnit), notes(notes), automaticSilence(automaticSilence){}; + + /** + * Return the title of the melody. + */ + String getTitle() const { + return title; + }; + + /** + * Return the time unit (i.e. the minimum length of a note), in milliseconds. + */ + unsigned short getTimeUnit() const { + return timeUnit; + }; + + /** + * Get the number of notes. + */ + unsigned short getLength() const { + if (notes == nullptr) return 0; + return (*notes).size(); + } + + /** + * Get the note at the given position. + * If the melody or the position is invalid, return a zeroed NoteDuration. + */ + NoteDuration getNote(unsigned short i) const { + if (i < getLength()) { return (*notes)[i]; } + return { 0, 0 }; + } + + /** + * Return true if the melody should be played with a small delay between each note. + */ + bool getAutomaticSilence() const { + return automaticSilence; + } + + /** + * Return true if the melody is valid, false otherwise. + */ + bool isValid() const { + return notes != nullptr && (*notes).size() != 0; + } + + /** + * Return true if the melody is valid, false otherwise. + */ + explicit operator bool() const { + return isValid(); + } + +private: + String title; + // in milliseconds + unsigned short timeUnit; + std::shared_ptr> notes; + const static unsigned short maxLength = 1000; + bool automaticSilence; + + // Enable debug messages over serial port + const static bool debug = false; +}; + +#endif // END MELODY_H diff --git a/lib/Melody Player/src/melody_factory.cpp b/src/MelodyPlayer/melody_factory.cpp similarity index 97% rename from lib/Melody Player/src/melody_factory.cpp rename to src/MelodyPlayer/melody_factory.cpp index 10da83de..25d6a999 100644 --- a/lib/Melody Player/src/melody_factory.cpp +++ b/src/MelodyPlayer/melody_factory.cpp @@ -1,229 +1,229 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#include "melody_factory.h" -#include "notes_array.h" -#include -#include - -static void removeCarriageReturn(String& s) { - if (s.charAt(s.length() - 1) == '\r') { s = s.substring(0, s.length() - 1); } -} - -Melody MelodyFactoryClass::load(String filepath, FS& fs) { - File f = LittleFS.open(filepath, "r"); - f.setTimeout(0); - - if (!f) { - if (debug) Serial.println("Opening file error"); - return Melody(); - } - - // Skip multi-line comments at the begin of the file - String line = f.readStringUntil('\n'); - while (line.charAt(0) == '#') { line = f.readStringUntil('\n'); } - - bool success = false; - success = loadTitle(line); - if (!success) { return Melody(); } - - success = loadTimeUnit(f.readStringUntil('\n')); - if (!success) { return Melody(); } - - success = loadNumberOfNotes(f.readStringUntil('\n')); - if (!success) { return Melody(); } - - NoteFormat noteFormat = loadNoteFormat(f.readStringUntil('\n')); - if (noteFormat == NoteFormat::ERROR) { - return Melody(); - } else { - this->noteFormat = noteFormat; - } - - if (debug) - Serial.println(String("This melody object will take at least: ") + (sizeof(NoteDuration) * nNotes) + "bytes"); - if (nNotes < maxLength) { - notes = std::make_shared>(); - notes->reserve(nNotes); - bool error = false; - while (f.available() && notes->size() < nNotes && !error) { - // get a token - String noteDuration = f.readStringUntil('|'); - error = !loadNote(noteDuration); - } - - if (error) { - if (debug) Serial.println("error during the tokens loading!"); - return Melody(); - } - } - - return Melody(title, timeUnit, notes, true); -} - -Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, String notesToLoad[], - unsigned short nNotesToLoad, bool autoSilence) { - if (title.length() == 0 && timeUnit <= 20) { return Melody(); } - if (nNotesToLoad == 0 || nNotesToLoad > maxLength) { return Melody(); } - - if (notesToLoad == nullptr) { return Melody(); } - - notes = std::make_shared>(); - notes->reserve(nNotesToLoad); - noteFormat = NoteFormat::STRING; - bool error = false; - while (this->notes->size() < nNotesToLoad && !error) { - String noteDuration = notesToLoad[notes->size()] + ",1"; - error = !loadNote(noteDuration); - } - if (error) { return Melody(); } - - return Melody(title, timeUnit, notes, autoSilence); -} - -Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, int frequenciesToLoad[], - unsigned short nFrequenciesToLoad, bool autoSilence) { - if (title.length() == 0 && timeUnit <= 20) { return Melody(); } - if (nFrequenciesToLoad == 0 || nFrequenciesToLoad > maxLength) { return Melody(); } - - if (frequenciesToLoad == nullptr) { return Melody(); } - - notes = std::make_shared>(); - notes->reserve(nFrequenciesToLoad); - noteFormat = NoteFormat::INTEGER; - bool error = false; - while (this->notes->size() < nFrequenciesToLoad && !error) { - String noteDuration = String(frequenciesToLoad[notes->size()]) + ",1"; - error = !loadNote(noteDuration); - } - if (error) { return Melody(); } - - return Melody(title, timeUnit, notes, autoSilence); -} - -bool MelodyFactoryClass::loadTitle(String line) { - removeCarriageReturn(line); - if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); - if (line.startsWith("title")) { - // Skip also '=' - String title = line.substring(6); - this->title = title; - return true; - } - return false; -} - -bool MelodyFactoryClass::loadTimeUnit(String line) { - removeCarriageReturn(line); - if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); - if (line.startsWith("timeUnit")) { - // Skip '=' - String t = line.substring(9); - this->timeUnit = t.toInt(); - if (debug) Serial.println(this->timeUnit); - if (this->timeUnit > 20) { return true; } - } - return false; -} - -bool MelodyFactoryClass::loadNumberOfNotes(String line) { - removeCarriageReturn(line); - if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); - if (line.startsWith("length")) { - // Skip also '=' - String len = line.substring(7); - this->nNotes = len.toInt(); - if (debug) Serial.println(this->nNotes); - return true; - } - return false; -} - -MelodyFactoryClass::NoteFormat MelodyFactoryClass::loadNoteFormat(String line) { - removeCarriageReturn(line); - if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); - String format; - if (line.startsWith("format")) { - // Skip also '=' - format = line.substring(7); - if (debug) Serial.println(format); - } - - NoteFormat noteFormat = NoteFormat::ERROR; - if (format == "string") { - noteFormat = NoteFormat::STRING; - } else if (format == "integer") { - noteFormat = NoteFormat::INTEGER; - } - - return noteFormat; -} - -bool MelodyFactoryClass::loadNote(String token) { - token.trim(); - NoteDuration note; - - if (debug) Serial.println(String("note+duration: ") + token); - - String aux; - unsigned int j = 0; - - // Get the frequency - while (j < token.length() && token.charAt(j) != ',') { - aux += token.charAt(j); - j++; - } - - if (noteFormat == NoteFormat::STRING) { - auto n = std::find_if(noteMapping.cbegin(), noteMapping.cend(), - [&aux](std::pair e) { - return e.first == aux.c_str(); - }); - - if (n != noteMapping.cend()) { - note.frequency = n->second; - } else { - if (debug) Serial.println(String("This note doesn't exist: ") + aux); - return false; - } - - } else if (noteFormat == NoteFormat::INTEGER) { - note.frequency = aux.toInt(); - } - if (debug) Serial.println(String("freq: ") + note.frequency); - - j++; - aux = ""; - while (j < token.length()) { - aux += token.charAt(j); - j++; - } - - note.duration = aux.toInt(); - - if (debug) Serial.println(String("duration: ") + note.duration); - // The representation of relative note duration is fixed-point with decimal part length = 1bit - note.duration *= 2; - - notes->push_back(note); - - return true; -} - -MelodyFactoryClass MelodyFactory; +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#include "melody_factory.h" +#include "notes_array.h" +#include +#include + +static void removeCarriageReturn(String& s) { + if (s.charAt(s.length() - 1) == '\r') { s = s.substring(0, s.length() - 1); } +} + +Melody MelodyFactoryClass::load(String filepath, FS& fs) { + File f = LittleFS.open(filepath, "r"); + f.setTimeout(0); + + if (!f) { + if (debug) Serial.println("Opening file error"); + return Melody(); + } + + // Skip multi-line comments at the begin of the file + String line = f.readStringUntil('\n'); + while (line.charAt(0) == '#') { line = f.readStringUntil('\n'); } + + bool success = false; + success = loadTitle(line); + if (!success) { return Melody(); } + + success = loadTimeUnit(f.readStringUntil('\n')); + if (!success) { return Melody(); } + + success = loadNumberOfNotes(f.readStringUntil('\n')); + if (!success) { return Melody(); } + + NoteFormat noteFormat = loadNoteFormat(f.readStringUntil('\n')); + if (noteFormat == NoteFormat::ERROR) { + return Melody(); + } else { + this->noteFormat = noteFormat; + } + + if (debug) + Serial.println(String("This melody object will take at least: ") + (sizeof(NoteDuration) * nNotes) + "bytes"); + if (nNotes < maxLength) { + notes = std::make_shared>(); + notes->reserve(nNotes); + bool error = false; + while (f.available() && notes->size() < nNotes && !error) { + // get a token + String noteDuration = f.readStringUntil('|'); + error = !loadNote(noteDuration); + } + + if (error) { + if (debug) Serial.println("error during the tokens loading!"); + return Melody(); + } + } + + return Melody(title, timeUnit, notes, true); +} + +Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, String notesToLoad[], + unsigned short nNotesToLoad, bool autoSilence) { + if (title.length() == 0 && timeUnit <= 20) { return Melody(); } + if (nNotesToLoad == 0 || nNotesToLoad > maxLength) { return Melody(); } + + if (notesToLoad == nullptr) { return Melody(); } + + notes = std::make_shared>(); + notes->reserve(nNotesToLoad); + noteFormat = NoteFormat::STRING; + bool error = false; + while (this->notes->size() < nNotesToLoad && !error) { + String noteDuration = notesToLoad[notes->size()] + ",1"; + error = !loadNote(noteDuration); + } + if (error) { return Melody(); } + + return Melody(title, timeUnit, notes, autoSilence); +} + +Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, int frequenciesToLoad[], + unsigned short nFrequenciesToLoad, bool autoSilence) { + if (title.length() == 0 && timeUnit <= 20) { return Melody(); } + if (nFrequenciesToLoad == 0 || nFrequenciesToLoad > maxLength) { return Melody(); } + + if (frequenciesToLoad == nullptr) { return Melody(); } + + notes = std::make_shared>(); + notes->reserve(nFrequenciesToLoad); + noteFormat = NoteFormat::INTEGER; + bool error = false; + while (this->notes->size() < nFrequenciesToLoad && !error) { + String noteDuration = String(frequenciesToLoad[notes->size()]) + ",1"; + error = !loadNote(noteDuration); + } + if (error) { return Melody(); } + + return Melody(title, timeUnit, notes, autoSilence); +} + +bool MelodyFactoryClass::loadTitle(String line) { + removeCarriageReturn(line); + if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); + if (line.startsWith("title")) { + // Skip also '=' + String title = line.substring(6); + this->title = title; + return true; + } + return false; +} + +bool MelodyFactoryClass::loadTimeUnit(String line) { + removeCarriageReturn(line); + if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); + if (line.startsWith("timeUnit")) { + // Skip '=' + String t = line.substring(9); + this->timeUnit = t.toInt(); + if (debug) Serial.println(this->timeUnit); + if (this->timeUnit > 20) { return true; } + } + return false; +} + +bool MelodyFactoryClass::loadNumberOfNotes(String line) { + removeCarriageReturn(line); + if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); + if (line.startsWith("length")) { + // Skip also '=' + String len = line.substring(7); + this->nNotes = len.toInt(); + if (debug) Serial.println(this->nNotes); + return true; + } + return false; +} + +MelodyFactoryClass::NoteFormat MelodyFactoryClass::loadNoteFormat(String line) { + removeCarriageReturn(line); + if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length()); + String format; + if (line.startsWith("format")) { + // Skip also '=' + format = line.substring(7); + if (debug) Serial.println(format); + } + + NoteFormat noteFormat = NoteFormat::ERROR; + if (format == "string") { + noteFormat = NoteFormat::STRING; + } else if (format == "integer") { + noteFormat = NoteFormat::INTEGER; + } + + return noteFormat; +} + +bool MelodyFactoryClass::loadNote(String token) { + token.trim(); + NoteDuration note; + + if (debug) Serial.println(String("note+duration: ") + token); + + String aux; + unsigned int j = 0; + + // Get the frequency + while (j < token.length() && token.charAt(j) != ',') { + aux += token.charAt(j); + j++; + } + + if (noteFormat == NoteFormat::STRING) { + auto n = std::find_if(noteMapping.cbegin(), noteMapping.cend(), + [&aux](std::pair e) { + return e.first == aux.c_str(); + }); + + if (n != noteMapping.cend()) { + note.frequency = n->second; + } else { + if (debug) Serial.println(String("This note doesn't exist: ") + aux); + return false; + } + + } else if (noteFormat == NoteFormat::INTEGER) { + note.frequency = aux.toInt(); + } + if (debug) Serial.println(String("freq: ") + note.frequency); + + j++; + aux = ""; + while (j < token.length()) { + aux += token.charAt(j); + j++; + } + + note.duration = aux.toInt(); + + if (debug) Serial.println(String("duration: ") + note.duration); + // The representation of relative note duration is fixed-point with decimal part length = 1bit + note.duration *= 2; + + notes->push_back(note); + + return true; +} + +MelodyFactoryClass MelodyFactory; diff --git a/lib/Melody Player/src/melody_factory.h b/src/MelodyPlayer/melody_factory.h similarity index 97% rename from lib/Melody Player/src/melody_factory.h rename to src/MelodyPlayer/melody_factory.h index 3924eec7..2bc4c131 100644 --- a/lib/Melody Player/src/melody_factory.h +++ b/src/MelodyPlayer/melody_factory.h @@ -1,163 +1,163 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#ifndef MELODY_FACTORY_H -#define MELODY_FACTORY_H - -#include "melody.h" - -#include -#ifdef ESP32 -#include -#endif - -class MelodyFactoryClass { -public: - /** - * Load the melody from file in MelodyPlayer format. - */ - Melody load(String filePath, FS& fs = SPIFFS); - - /** - * Load melody from file in RTTTL format. The file must contain only one melody. - */ - Melody loadRtttlFile(String filePath, FS& fs = SPIFFS); - - /** - * Load melody with the given title from a file containing multiple RTTTL melody (one Melody per - * line). - */ - Melody loadRtttlDB(String filepath, String title, FS& fs = SPIFFS); - - /** - * Load melody from string in RTTTL format. - */ - Melody loadRtttlString(const char rtttlMelody[]); - - /** - * Create a melody with the given parameters. - * Notes are represented as string accordigly to english notation (i.e. "C4", "G3", "G6"). - * This method assumes that each note lasts 1 beat. - * frequenciesToLoad are integer numbers expressing the real reproduced frequency. - * automaticSilence, if true, automatically inserts a small silence between 2 consecutive notes. - */ - Melody load(String title, unsigned short timeUnit, String notesToLoad[], - unsigned short nNotesToLoad, bool autoSilence = true); - - /** - * Create a melody with the given parameters. - * This method assumes that each note lasts 1 beat. - * frequenciesToLoad are integer numbers expressing the real reproduced frequency. - * The last parameter, automaticSilence, if true, automatically inserts a small silence between 2 - * consecutive notes. - */ - Melody load(String title, unsigned short timeUnit, int frequenciesToLoad[], - unsigned short nFrequenciesToLoad, bool autoSilence = true); - -private: - enum class NoteFormat { ERROR, STRING, INTEGER }; - - String title; - unsigned short timeUnit; - NoteFormat noteFormat; - std::shared_ptr> notes; - // Used to check how many notes are stored in a file. - unsigned short nNotes; - const unsigned short maxLength = 1000; - - ///////////// RTTTL helpers - /** - * The default duration of a note. For example, - * "4" means that each note with no duration specifier - * is by default considered a quarter note. Possibile values: - * 1 - whole note - * 2 - half note - * 4 - quarter note - * 8 - eighth note - * 16 - sixteenth note - * 32 - thirty-second note - */ - const unsigned short defaultDuration = 4; - unsigned short duration; - - /** - * The default octave. There are four octaves in the RTTTL format [4-7]. - */ - const unsigned short defaultOctave = 6; - unsigned short octave; - - /** - * The default BPM (beats per minute) value. BPM is arbitrarily limited between 10 and 300. Look - * at the implementation of parseBeat for more info. - */ - const unsigned short defaultBeat = 63; - unsigned short beat; - - /** - * Try to parse the default parameters of RTTTL melody. - * If user-defined defaults are not found it sets the default values as prescribed by RTTTL - * specification. - */ - void parseDefaultValues(String values); - - unsigned int parseDuration(const String& s, int& startFrom); - unsigned int parseOctave(const String& s, int& startFrom); - unsigned int parseBeat(const String& s, int& startFrom); - bool parseRtttlNote(const String& s); - - //////////// END RTTTL helpers - - /** - * Parse the title from the given string. - * Return true on success. - */ - bool loadTitle(String line); - - /** - * Parse the time unit from the given string. - * Return true on success. - */ - bool loadTimeUnit(String line); - - /** - * Parse the number of notes from the given string. - * Return true on success. - */ - bool loadNumberOfNotes(String line); - - /** - * Parse the note's format from the given string. - */ - NoteFormat loadNoteFormat(String line); - - /** - * Parse a token (a note and its duration) from the given string. - * The format of this token is: - * + ',' + . - * Return true if the parsing succeeds, false otherwise. - */ - bool loadNote(String token); - - // Enable debug messages over serial port - static const bool debug = false; -}; - -extern MelodyFactoryClass MelodyFactory; - -#endif // END MELODY_FACTORY_H +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#ifndef MELODY_FACTORY_H +#define MELODY_FACTORY_H + +#include "melody.h" + +#include +#ifdef ESP32 +#include +#endif + +class MelodyFactoryClass { +public: + /** + * Load the melody from file in MelodyPlayer format. + */ + Melody load(String filePath, FS& fs = SPIFFS); + + /** + * Load melody from file in RTTTL format. The file must contain only one melody. + */ + Melody loadRtttlFile(String filePath, FS& fs = SPIFFS); + + /** + * Load melody with the given title from a file containing multiple RTTTL melody (one Melody per + * line). + */ + Melody loadRtttlDB(String filepath, String title, FS& fs = SPIFFS); + + /** + * Load melody from string in RTTTL format. + */ + Melody loadRtttlString(const char rtttlMelody[]); + + /** + * Create a melody with the given parameters. + * Notes are represented as string accordigly to english notation (i.e. "C4", "G3", "G6"). + * This method assumes that each note lasts 1 beat. + * frequenciesToLoad are integer numbers expressing the real reproduced frequency. + * automaticSilence, if true, automatically inserts a small silence between 2 consecutive notes. + */ + Melody load(String title, unsigned short timeUnit, String notesToLoad[], + unsigned short nNotesToLoad, bool autoSilence = true); + + /** + * Create a melody with the given parameters. + * This method assumes that each note lasts 1 beat. + * frequenciesToLoad are integer numbers expressing the real reproduced frequency. + * The last parameter, automaticSilence, if true, automatically inserts a small silence between 2 + * consecutive notes. + */ + Melody load(String title, unsigned short timeUnit, int frequenciesToLoad[], + unsigned short nFrequenciesToLoad, bool autoSilence = true); + +private: + enum class NoteFormat { ERROR, STRING, INTEGER }; + + String title; + unsigned short timeUnit; + NoteFormat noteFormat; + std::shared_ptr> notes; + // Used to check how many notes are stored in a file. + unsigned short nNotes; + const unsigned short maxLength = 1000; + + ///////////// RTTTL helpers + /** + * The default duration of a note. For example, + * "4" means that each note with no duration specifier + * is by default considered a quarter note. Possibile values: + * 1 - whole note + * 2 - half note + * 4 - quarter note + * 8 - eighth note + * 16 - sixteenth note + * 32 - thirty-second note + */ + const unsigned short defaultDuration = 4; + unsigned short duration; + + /** + * The default octave. There are four octaves in the RTTTL format [4-7]. + */ + const unsigned short defaultOctave = 6; + unsigned short octave; + + /** + * The default BPM (beats per minute) value. BPM is arbitrarily limited between 10 and 300. Look + * at the implementation of parseBeat for more info. + */ + const unsigned short defaultBeat = 63; + unsigned short beat; + + /** + * Try to parse the default parameters of RTTTL melody. + * If user-defined defaults are not found it sets the default values as prescribed by RTTTL + * specification. + */ + void parseDefaultValues(String values); + + unsigned int parseDuration(const String& s, int& startFrom); + unsigned int parseOctave(const String& s, int& startFrom); + unsigned int parseBeat(const String& s, int& startFrom); + bool parseRtttlNote(const String& s); + + //////////// END RTTTL helpers + + /** + * Parse the title from the given string. + * Return true on success. + */ + bool loadTitle(String line); + + /** + * Parse the time unit from the given string. + * Return true on success. + */ + bool loadTimeUnit(String line); + + /** + * Parse the number of notes from the given string. + * Return true on success. + */ + bool loadNumberOfNotes(String line); + + /** + * Parse the note's format from the given string. + */ + NoteFormat loadNoteFormat(String line); + + /** + * Parse a token (a note and its duration) from the given string. + * The format of this token is: + * + ',' + . + * Return true if the parsing succeeds, false otherwise. + */ + bool loadNote(String token); + + // Enable debug messages over serial port + static const bool debug = false; +}; + +extern MelodyFactoryClass MelodyFactory; + +#endif // END MELODY_FACTORY_H diff --git a/lib/Melody Player/src/melody_factory_rtttl.cpp b/src/MelodyPlayer/melody_factory_rtttl.cpp similarity index 96% rename from lib/Melody Player/src/melody_factory_rtttl.cpp rename to src/MelodyPlayer/melody_factory_rtttl.cpp index 1ebbf48a..9077813d 100644 --- a/lib/Melody Player/src/melody_factory_rtttl.cpp +++ b/src/MelodyPlayer/melody_factory_rtttl.cpp @@ -1,368 +1,368 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#include "melody_factory.h" -#include "notes.h" -#include -// clang-format off -const uint16_t sourceNotes[] = { - 0, - NOTE_C4, - NOTE_CS4, - NOTE_D4, - NOTE_DS4, - NOTE_E4, - NOTE_F4, - NOTE_FS4, - NOTE_G4, - NOTE_GS4, - NOTE_A4, - NOTE_AS4, - NOTE_B4, - - NOTE_C5, - NOTE_CS5, - NOTE_D5, - NOTE_DS5, - NOTE_E5, - NOTE_F5, - NOTE_FS5, - NOTE_G5, - NOTE_GS5, - NOTE_A5, - NOTE_AS5, - NOTE_B5, - - NOTE_C6, - NOTE_CS6, - NOTE_D6, - NOTE_DS6, - NOTE_E6, - NOTE_F6, - NOTE_FS6, - NOTE_G6, - NOTE_GS6, - NOTE_A6, - NOTE_AS6, - NOTE_B6, - - NOTE_C7, - NOTE_CS7, - NOTE_D7, - NOTE_DS7, - NOTE_E7, - NOTE_F7, - NOTE_FS7, - NOTE_G7, - NOTE_GS7, - NOTE_A7, - NOTE_AS7, - NOTE_B7, - - 2 * NOTE_C7, - 2 * NOTE_CS7, - 2 * NOTE_D7, - 2 * NOTE_DS7, - 2 * NOTE_E7, - 2 * NOTE_F7, - 2 * NOTE_FS7, - 2 * NOTE_G7, - 2 * NOTE_GS7, - 2 * NOTE_A7, - 2 * NOTE_AS7, - 2 * NOTE_B7, -}; -// clang-format on - -Melody MelodyFactoryClass::loadRtttlFile(String filepath, FS& fs) { - File f = LittleFS.open(filepath, "r"); - f.setTimeout(0); - - if (!f) { - if (debug) Serial.println("Opening file error"); - return Melody(); - } - - String title = f.readStringUntil(':'); - title.trim(); - if (debug) Serial.println(String("Title:") + title); - if (title.length() == 0) { return Melody(); } - - String values = f.readStringUntil(':'); - values.trim(); - if (debug) Serial.println(String("Default values:") + values); - if (values.length() == 0) { return Melody(); } - - parseDefaultValues(values); - - // 32 because it is the shortest note! - int timeUnit = 60 * 1000 * 4 / beat / 32; - - notes = std::make_shared>(); - bool result = true; - while (f.available() && notes->size() < maxLength && result) { - String s = f.readStringUntil(','); - s.trim(); - result = parseRtttlNote(s); - } - if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); } - - return Melody(); -} - -Melody MelodyFactoryClass::loadRtttlDB(String filepath, String title, FS& fs) { - File f = LittleFS.open(filepath, "r"); - f.setTimeout(0); - - if (!f) { - if (debug) Serial.println("Opening file error"); - return Melody(); - } - - if (title.length() == 0) { - if (debug) Serial.println("Title length = 0"); - return Melody(); - } - - if (!f.find(title.c_str())) { - if (debug) Serial.println("Unable to find melody with title: " + String(title)); - return Melody(); - } - f.readStringUntil(':'); - - String values = f.readStringUntil(':'); - values.trim(); - if (debug) Serial.println(String("Default values:") + values); - if (values.length() == 0) { return Melody(); } - - parseDefaultValues(values); - - // 32 because it is the shortest note! - int timeUnit = 60 * 1000 * 4 / beat / 32; - - size_t position = f.position(); - int bytesUntilNewLine = f.readStringUntil('\n').length(); - f.seek(position); - - notes = std::make_shared>(); - bool result = true; - while (f.available() && notes->size() < maxLength && result && bytesUntilNewLine > 0) { - String s = f.readStringUntil(','); - if (s.length() > bytesUntilNewLine) { s = s.substring(0, bytesUntilNewLine); } - bytesUntilNewLine -= s.length() + 1; - s.trim(); - result = parseRtttlNote(s); - } - if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); } - - return Melody(); -} - -Melody MelodyFactoryClass::loadRtttlString(const char rtttlMelody[]) { - String title; - int i = 0; - while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') { - title.concat(rtttlMelody[i]); - i++; - } - - if (title.length() == 0 || rtttlMelody[i] == 0) { return Melody(); } - - // skip ':' - i++; - - String defaultParameters; - while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') { - defaultParameters.concat(rtttlMelody[i]); - i++; - } - - if (rtttlMelody[i] == 0) { return Melody(); } - - defaultParameters.trim(); - parseDefaultValues(defaultParameters); - - // 32 because it is the shortest note! - int timeUnit = 60 * 1000 * 4 / beat / 32; - - // skip ':' - i++; - - notes = std::make_shared>(); - // Read notes - while (rtttlMelody[i] != 0) { - String note; - while (rtttlMelody[i] != 0 && rtttlMelody[i] != ',') { - note.concat(rtttlMelody[i]); - i++; - } - note.trim(); - parseRtttlNote(note); - if (rtttlMelody[i] == ',') { i++; } - } - if (notes->size() > 0) { return Melody(title, timeUnit, notes, false); } - - return Melody(); -} - -/** - * Parse an unsigned integer starting from the given startFrom to the first non-digit char. - * Return zero if it cannot parse a number. *startFrom* will point to the first non-digit char. - */ -unsigned int getUnsignedInt(const String& s, int& startFrom) { - unsigned int temp = 0; - while (isDigit(s.charAt(startFrom))) { - temp = (temp * 10) + s.charAt(startFrom) - '0'; - startFrom++; - } - return temp; -} - -unsigned int MelodyFactoryClass::parseDuration(const String& s, int& startFrom) { - // Skip '=' - startFrom++; - unsigned int temp = getUnsignedInt(s, startFrom); - if (temp != 1 && temp != 2 && temp != 4 && temp != 8 && temp != 16 && temp != 32) { return 0; } - // Discard ',' - startFrom++; - return temp; -} - -unsigned int MelodyFactoryClass::parseOctave(const String& s, int& startFrom) { - // Skip '=' - startFrom++; - unsigned int temp = getUnsignedInt(s, startFrom); - if (temp < 4 || temp > 7) { return 0; } - // Discard ',' - startFrom++; - return temp; -} - -unsigned int MelodyFactoryClass::parseBeat(const String& s, int& startFrom) { - // Skip '=' - startFrom++; - unsigned int temp = getUnsignedInt(s, startFrom); - - // BPM is arbitrarily limited to 300. You may try to increase it, but remember that - // actually, the minimum note length is 60(seconds)/300(bpm)/32(minimum note length) = 6.25ms. - // If you reduce this duration, you may not be able to keep up the pace to play a smooth - // async playback while doing other operations. - if (!(10 <= temp && temp <= 300)) { return 0; } - // Discard ',' - startFrom++; - return temp; -} - -bool MelodyFactoryClass::parseRtttlNote(const String& s) { - int i = 0; - - unsigned short relativeDuration = this->duration; - // Optional number: note duration (e.g 4=quarter note, ...) - if (isdigit(s.charAt(i))) { - unsigned int temp = getUnsignedInt(s, i); - if (temp) { relativeDuration = temp; } - } - - // To match struct NoteDuration format, I need the direct - // note length, instead RTTTL provides the denominator - // of the whole note - if (relativeDuration == 32) { - relativeDuration = 1; - } else if (relativeDuration == 16) { - relativeDuration = 2; - } else if (relativeDuration == 8) { - relativeDuration = 4; - } else if (relativeDuration == 4) { - relativeDuration = 8; - } else if (relativeDuration == 2) { - relativeDuration = 16; - } else if (relativeDuration == 1) { - relativeDuration = 32; - } else { - relativeDuration = 0; - } - - // note (p is silence) - int note = 0; - switch (s.charAt(i)) { - case 'c': note = 1; break; - case 'd': note = 3; break; - case 'e': note = 5; break; - case 'f': note = 6; break; - case 'g': note = 8; break; - case 'a': note = 10; break; - case 'b': note = 12; break; - case 'p': - default: note = 0; - } - - i++; - - // Optional # - if (s.charAt(i) == '#') { - note++; - i++; - } - - // The representation of relative note duration is fixed-point with decimal part length = 1bit - relativeDuration *= 2; - // get optional '.' dotted note - // This note will last 50% more - if (s.charAt(i) == '.') { - relativeDuration += relativeDuration / 2; - i++; - } - - int scale; - // now, get scale - if (isdigit(s.charAt(i))) { - scale = s.charAt(i) - '0'; - i++; - } else { - scale = octave; - } - - unsigned short freq; - if (note) { - freq = sourceNotes[(scale - 4) * 12 + note]; - } else { - freq = 0; - } - - notes->push_back({ .frequency = freq, .duration = relativeDuration }); - return true; -} - -void MelodyFactoryClass::parseDefaultValues(String values) { - int i = 0; - - if (values.charAt(i) == 'd') { i++; } - duration = parseDuration(values, i); - if (duration == 0) { duration = defaultDuration; } - - if (values.charAt(i) == 'o') { i++; } - octave = parseOctave(values, i); - if (octave == 0) { octave = defaultOctave; } - - if (values.charAt(i) == 'b') { - i++; - beat = parseBeat(values, i); - } - if (beat == 0) { beat = defaultBeat; } -} +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#include "melody_factory.h" +#include "notes.h" +#include +// clang-format off +const uint16_t sourceNotes[] = { + 0, + NOTE_C4, + NOTE_CS4, + NOTE_D4, + NOTE_DS4, + NOTE_E4, + NOTE_F4, + NOTE_FS4, + NOTE_G4, + NOTE_GS4, + NOTE_A4, + NOTE_AS4, + NOTE_B4, + + NOTE_C5, + NOTE_CS5, + NOTE_D5, + NOTE_DS5, + NOTE_E5, + NOTE_F5, + NOTE_FS5, + NOTE_G5, + NOTE_GS5, + NOTE_A5, + NOTE_AS5, + NOTE_B5, + + NOTE_C6, + NOTE_CS6, + NOTE_D6, + NOTE_DS6, + NOTE_E6, + NOTE_F6, + NOTE_FS6, + NOTE_G6, + NOTE_GS6, + NOTE_A6, + NOTE_AS6, + NOTE_B6, + + NOTE_C7, + NOTE_CS7, + NOTE_D7, + NOTE_DS7, + NOTE_E7, + NOTE_F7, + NOTE_FS7, + NOTE_G7, + NOTE_GS7, + NOTE_A7, + NOTE_AS7, + NOTE_B7, + + 2 * NOTE_C7, + 2 * NOTE_CS7, + 2 * NOTE_D7, + 2 * NOTE_DS7, + 2 * NOTE_E7, + 2 * NOTE_F7, + 2 * NOTE_FS7, + 2 * NOTE_G7, + 2 * NOTE_GS7, + 2 * NOTE_A7, + 2 * NOTE_AS7, + 2 * NOTE_B7, +}; +// clang-format on + +Melody MelodyFactoryClass::loadRtttlFile(String filepath, FS& fs) { + File f = LittleFS.open(filepath, "r"); + f.setTimeout(0); + + if (!f) { + if (debug) Serial.println("Opening file error"); + return Melody(); + } + + String title = f.readStringUntil(':'); + title.trim(); + if (debug) Serial.println(String("Title:") + title); + if (title.length() == 0) { return Melody(); } + + String values = f.readStringUntil(':'); + values.trim(); + if (debug) Serial.println(String("Default values:") + values); + if (values.length() == 0) { return Melody(); } + + parseDefaultValues(values); + + // 32 because it is the shortest note! + int timeUnit = 60 * 1000 * 4 / beat / 32; + + notes = std::make_shared>(); + bool result = true; + while (f.available() && notes->size() < maxLength && result) { + String s = f.readStringUntil(','); + s.trim(); + result = parseRtttlNote(s); + } + if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); } + + return Melody(); +} + +Melody MelodyFactoryClass::loadRtttlDB(String filepath, String title, FS& fs) { + File f = LittleFS.open(filepath, "r"); + f.setTimeout(0); + + if (!f) { + if (debug) Serial.println("Opening file error"); + return Melody(); + } + + if (title.length() == 0) { + if (debug) Serial.println("Title length = 0"); + return Melody(); + } + + if (!f.find(title.c_str())) { + if (debug) Serial.println("Unable to find melody with title: " + String(title)); + return Melody(); + } + f.readStringUntil(':'); + + String values = f.readStringUntil(':'); + values.trim(); + if (debug) Serial.println(String("Default values:") + values); + if (values.length() == 0) { return Melody(); } + + parseDefaultValues(values); + + // 32 because it is the shortest note! + int timeUnit = 60 * 1000 * 4 / beat / 32; + + size_t position = f.position(); + int bytesUntilNewLine = f.readStringUntil('\n').length(); + f.seek(position); + + notes = std::make_shared>(); + bool result = true; + while (f.available() && notes->size() < maxLength && result && bytesUntilNewLine > 0) { + String s = f.readStringUntil(','); + if (s.length() > bytesUntilNewLine) { s = s.substring(0, bytesUntilNewLine); } + bytesUntilNewLine -= s.length() + 1; + s.trim(); + result = parseRtttlNote(s); + } + if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); } + + return Melody(); +} + +Melody MelodyFactoryClass::loadRtttlString(const char rtttlMelody[]) { + String title; + int i = 0; + while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') { + title.concat(rtttlMelody[i]); + i++; + } + + if (title.length() == 0 || rtttlMelody[i] == 0) { return Melody(); } + + // skip ':' + i++; + + String defaultParameters; + while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') { + defaultParameters.concat(rtttlMelody[i]); + i++; + } + + if (rtttlMelody[i] == 0) { return Melody(); } + + defaultParameters.trim(); + parseDefaultValues(defaultParameters); + + // 32 because it is the shortest note! + int timeUnit = 60 * 1000 * 4 / beat / 32; + + // skip ':' + i++; + + notes = std::make_shared>(); + // Read notes + while (rtttlMelody[i] != 0) { + String note; + while (rtttlMelody[i] != 0 && rtttlMelody[i] != ',') { + note.concat(rtttlMelody[i]); + i++; + } + note.trim(); + parseRtttlNote(note); + if (rtttlMelody[i] == ',') { i++; } + } + if (notes->size() > 0) { return Melody(title, timeUnit, notes, false); } + + return Melody(); +} + +/** + * Parse an unsigned integer starting from the given startFrom to the first non-digit char. + * Return zero if it cannot parse a number. *startFrom* will point to the first non-digit char. + */ +unsigned int getUnsignedInt(const String& s, int& startFrom) { + unsigned int temp = 0; + while (isDigit(s.charAt(startFrom))) { + temp = (temp * 10) + s.charAt(startFrom) - '0'; + startFrom++; + } + return temp; +} + +unsigned int MelodyFactoryClass::parseDuration(const String& s, int& startFrom) { + // Skip '=' + startFrom++; + unsigned int temp = getUnsignedInt(s, startFrom); + if (temp != 1 && temp != 2 && temp != 4 && temp != 8 && temp != 16 && temp != 32) { return 0; } + // Discard ',' + startFrom++; + return temp; +} + +unsigned int MelodyFactoryClass::parseOctave(const String& s, int& startFrom) { + // Skip '=' + startFrom++; + unsigned int temp = getUnsignedInt(s, startFrom); + if (temp < 4 || temp > 7) { return 0; } + // Discard ',' + startFrom++; + return temp; +} + +unsigned int MelodyFactoryClass::parseBeat(const String& s, int& startFrom) { + // Skip '=' + startFrom++; + unsigned int temp = getUnsignedInt(s, startFrom); + + // BPM is arbitrarily limited to 300. You may try to increase it, but remember that + // actually, the minimum note length is 60(seconds)/300(bpm)/32(minimum note length) = 6.25ms. + // If you reduce this duration, you may not be able to keep up the pace to play a smooth + // async playback while doing other operations. + if (!(10 <= temp && temp <= 300)) { return 0; } + // Discard ',' + startFrom++; + return temp; +} + +bool MelodyFactoryClass::parseRtttlNote(const String& s) { + int i = 0; + + unsigned short relativeDuration = this->duration; + // Optional number: note duration (e.g 4=quarter note, ...) + if (isdigit(s.charAt(i))) { + unsigned int temp = getUnsignedInt(s, i); + if (temp) { relativeDuration = temp; } + } + + // To match struct NoteDuration format, I need the direct + // note length, instead RTTTL provides the denominator + // of the whole note + if (relativeDuration == 32) { + relativeDuration = 1; + } else if (relativeDuration == 16) { + relativeDuration = 2; + } else if (relativeDuration == 8) { + relativeDuration = 4; + } else if (relativeDuration == 4) { + relativeDuration = 8; + } else if (relativeDuration == 2) { + relativeDuration = 16; + } else if (relativeDuration == 1) { + relativeDuration = 32; + } else { + relativeDuration = 0; + } + + // note (p is silence) + int note = 0; + switch (s.charAt(i)) { + case 'c': note = 1; break; + case 'd': note = 3; break; + case 'e': note = 5; break; + case 'f': note = 6; break; + case 'g': note = 8; break; + case 'a': note = 10; break; + case 'b': note = 12; break; + case 'p': + default: note = 0; + } + + i++; + + // Optional # + if (s.charAt(i) == '#') { + note++; + i++; + } + + // The representation of relative note duration is fixed-point with decimal part length = 1bit + relativeDuration *= 2; + // get optional '.' dotted note + // This note will last 50% more + if (s.charAt(i) == '.') { + relativeDuration += relativeDuration / 2; + i++; + } + + int scale; + // now, get scale + if (isdigit(s.charAt(i))) { + scale = s.charAt(i) - '0'; + i++; + } else { + scale = octave; + } + + unsigned short freq; + if (note) { + freq = sourceNotes[(scale - 4) * 12 + note]; + } else { + freq = 0; + } + + notes->push_back({ .frequency = freq, .duration = relativeDuration }); + return true; +} + +void MelodyFactoryClass::parseDefaultValues(String values) { + int i = 0; + + if (values.charAt(i) == 'd') { i++; } + duration = parseDuration(values, i); + if (duration == 0) { duration = defaultDuration; } + + if (values.charAt(i) == 'o') { i++; } + octave = parseOctave(values, i); + if (octave == 0) { octave = defaultOctave; } + + if (values.charAt(i) == 'b') { + i++; + beat = parseBeat(values, i); + } + if (beat == 0) { beat = defaultBeat; } +} diff --git a/lib/Melody Player/src/melody_player.cpp b/src/MelodyPlayer/melody_player.cpp similarity index 71% rename from lib/Melody Player/src/melody_player.cpp rename to src/MelodyPlayer/melody_player.cpp index 1d7c08ef..2048a10a 100644 --- a/lib/Melody Player/src/melody_player.cpp +++ b/src/MelodyPlayer/melody_player.cpp @@ -1,274 +1,341 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#include "melody_player.h" - -/** - * https://stackoverflow.com/questions/24609271/errormake-unique-is-not-a-member-of-std - */ -template std::unique_ptr make_unique(Args&&... args) { - return std::unique_ptr(new T(std::forward(args)...)); -} - -void MelodyPlayer::play() { - if (melodyState == nullptr) { return; } - - turnOn(); - state = State::PLAY; - - melodyState->advance(); - while (melodyState->getIndex() + melodyState->isSilence() < melodyState->melody.getLength()) { - NoteDuration computedNote = melodyState->getCurrentComputedNote(); - if (debug) - Serial.println(String("Playing: frequency:") + computedNote.frequency - + " duration:" + computedNote.duration); - if (melodyState->isSilence()) { -#ifdef ESP32 - ledcWriteTone(pwmChannel, 0); -#else - noTone(pin); -#endif - delay(0.3f * computedNote.duration); - } else { -#ifdef ESP32 - ledcWriteTone(pwmChannel, computedNote.frequency); -#else - tone(pin, computedNote.frequency); -#endif - delay(computedNote.duration); - } - melodyState->advance(); - } - stop(); -} - -void MelodyPlayer::play(Melody& melody) { - if (!melody) { return; } - melodyState = make_unique(melody); - play(); -} - -void changeTone(MelodyPlayer* player) { - // The last silence is not reproduced - player->melodyState->advance(); - if (player->melodyState->getIndex() + player->melodyState->isSilence() - < player->melodyState->melody.getLength()) { - NoteDuration computedNote(player->melodyState->getCurrentComputedNote()); - - float duration = player->melodyState->getRemainingNoteDuration(); - if (duration > 0) { - player->melodyState->resetRemainingNoteDuration(); - } else { - if (player->melodyState->isSilence()) { - duration = 0.3f * computedNote.duration; - } else { - duration = computedNote.duration; - } - } - if (player->debug) - Serial.println(String("Playing async: freq=") + computedNote.frequency + " dur=" + duration - + " iteration=" + player->melodyState->getIndex()); - - if (player->melodyState->isSilence()) { - if(!player->muted) - { -#ifdef ESP32 - ledcWriteTone(player->pwmChannel, 0); -#else - tone(player->pin, 0); -#endif - } - -#ifdef ESP32 - player->ticker.once_ms(duration, changeTone, player); -#else - player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player)); -#endif - } else { - if(!player->muted) - { -#ifdef ESP32 - ledcWriteTone(player->pwmChannel, computedNote.frequency); - ledcWrite(player->pwmChannel,player->volume); -#else - tone(player->pin, computedNote.frequency); -#endif - } - -#ifdef ESP32 - player->ticker.once_ms(duration, changeTone, player); -#else - player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player)); -#endif - } - player->supportSemiNote = millis() + duration; - } else { - // End of the melody - player->stop(); - if(player->loop) - { // Loop mode => start over - player->playAsync(); - } - else if(player->stopCallback != NULL) - { - player->stopCallback(); - } - } -} - -void MelodyPlayer::playAsync() { - if (melodyState == nullptr) { return; } - - turnOn(); - state = State::PLAY; - - // Start immediately -#ifdef ESP32 - ticker.once(0, changeTone, this); -#else - ticker.once_scheduled(0, std::bind(changeTone, this)); -#endif -} - -void MelodyPlayer::playAsync(Melody& melody, bool loopMelody, void(*callback)(void)) { - if (!melody) { return; } - melodyState = make_unique(melody); - loop = loopMelody; - stopCallback = callback; - playAsync(); -} - -void MelodyPlayer::stop() { - if (melodyState == nullptr) { return; } - - haltPlay(); - state = State::STOP; - melodyState->reset(); -} - -void MelodyPlayer::pause() { - if (melodyState == nullptr) { return; } - - haltPlay(); - state = State::PAUSE; - melodyState->saveRemainingNoteDuration(supportSemiNote); -} - -void MelodyPlayer::transferMelodyTo(MelodyPlayer& destPlayer) { - if (melodyState == nullptr) { return; } - - destPlayer.stop(); - - bool playing = isPlaying(); - - haltPlay(); - state = State::STOP; - melodyState->saveRemainingNoteDuration(supportSemiNote); - destPlayer.melodyState = std::move(melodyState); - - if (playing) { - destPlayer.playAsync(); - } else { - destPlayer.state = state; - } -} - -void MelodyPlayer::duplicateMelodyTo(MelodyPlayer& destPlayer) { - if (melodyState == nullptr) { return; } - - destPlayer.stop(); - destPlayer.melodyState = make_unique(*(this->melodyState)); - destPlayer.melodyState->saveRemainingNoteDuration(supportSemiNote); - - if (isPlaying()) { - destPlayer.playAsync(); - } else { - destPlayer.state = state; - } -} - -#ifdef ESP32 -MelodyPlayer::MelodyPlayer(unsigned char pin, unsigned char pwmChannel, bool offLevel) - : pin(pin), pwmChannel(pwmChannel), offLevel(offLevel), state(State::STOP), melodyState(nullptr) { - pinMode(pin, OUTPUT); - digitalWrite(pin, offLevel); -}; -#else -MelodyPlayer::MelodyPlayer(unsigned char pin, bool offLevel) - : pin(pin), offLevel(offLevel), state(State::STOP), melodyState(nullptr) { - pinMode(pin, OUTPUT); - digitalWrite(pin, offLevel); -}; -#endif - -void MelodyPlayer::haltPlay() { - // Stop player, but do not reset the melodyState - ticker.detach(); - turnOff(); -} - -void MelodyPlayer::turnOn() { -#ifdef ESP32 - const int resolution = 8; - // 2000 is a frequency, it will be changed at the first play - ledcSetup(pwmChannel, 2000, resolution); - ledcAttachPin(pin, pwmChannel); - ledcWrite(pwmChannel, volume); -#endif -} - -void MelodyPlayer::setVolume(byte newVolume) { - volume = newVolume/2; -#ifdef ESP32 - if(state == State::PLAY) - { - ledcWrite(pwmChannel, volume); - } -#endif -} - -void MelodyPlayer::turnOff() { -#ifdef ESP32 - ledcWrite(pwmChannel, 0); - ledcDetachPin(pin); -#else - // Remember that this will set LOW output, it doesn't mean that buzzer is off (look at offLevel - // for more info). - noTone(pin); -#endif - - pinMode(pin, OUTPUT); - digitalWrite(pin, offLevel); -} - -void MelodyPlayer::mute() { - muted = true; -} - -void MelodyPlayer::unmute() { -#ifdef ESP32 - ledcAttachPin(pin, pwmChannel); -#endif - muted = false; -} - -void MelodyPlayer::changeTempo(int newTempo) { - if (melodyState == nullptr) { return; } - melodyState->changeTempo(newTempo); +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#include "melody_player.h" +#include "../Globals.h" +/** + * https://stackoverflow.com/questions/24609271/errormake-unique-is-not-a-member-of-std + */ +template +std::unique_ptr make_unique(Args &&...args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +void MelodyPlayer::play() +{ + if (melodyState == nullptr) + { + return; + } + + turnOn(); + state = State::PLAY; + + melodyState->advance(); + while (melodyState->getIndex() + melodyState->isSilence() < melodyState->melody.getLength()) + { + NoteDuration computedNote = melodyState->getCurrentComputedNote(); + if (debug) + Serial.println(String("Playing: frequency:") + computedNote.frequency + " duration:" + computedNote.duration); + if (melodyState->isSilence()) + { +#ifdef ESP32 + ledcWriteTone(pwmChannel, 0); +#else + noTone(pin); +#endif + delay(0.3f * computedNote.duration); + } + else + { +#ifdef ESP32 + ledcWriteTone(pwmChannel, computedNote.frequency); +#else + tone(pin, computedNote.frequency); +#endif + delay(computedNote.duration); + } + melodyState->advance(); + } + stop(); +} + +void MelodyPlayer::play(Melody &melody) +{ + if (!melody) + { + return; + } + melodyState = make_unique(melody); + play(); +} + +void changeTone(MelodyPlayer *player) +{ + // The last silence is not reproduced + player->melodyState->advance(); + if (player->melodyState->getIndex() + player->melodyState->isSilence() < player->melodyState->melody.getLength()) + { + NoteDuration computedNote(player->melodyState->getCurrentComputedNote()); + + float duration = player->melodyState->getRemainingNoteDuration(); + if (duration > 0) + { + player->melodyState->resetRemainingNoteDuration(); + } + else + { + if (player->melodyState->isSilence()) + { + duration = 0.3f * computedNote.duration; + } + else + { + duration = computedNote.duration; + } + } + if (player->debug) + Serial.println(String("Playing async: freq=") + computedNote.frequency + " dur=" + duration + " iteration=" + player->melodyState->getIndex()); + + if (player->melodyState->isSilence()) + { + if (!player->muted) + { +#ifdef ESP32 + ledcWriteTone(player->pwmChannel, 0); +#else + tone(player->pin, 0); +#endif + } + +#ifdef ESP32 + player->ticker.once_ms(duration, changeTone, player); +#else + player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player)); +#endif + } + else + { + if (!player->muted) + { +#ifdef ESP32 + ledcWriteTone(player->pwmChannel, computedNote.frequency); + if (BUZ_VOL) + ledcWrite(player->pwmChannel, player->volume); +#else + tone(player->pin, computedNote.frequency); +#endif + } + +#ifdef ESP32 + player->ticker.once_ms(duration, changeTone, player); +#else + player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player)); +#endif + } + player->supportSemiNote = millis() + duration; + } + else + { + // End of the melody + player->stop(); + if (player->loop) + { // Loop mode => start over + player->playAsync(); + } + else if (player->stopCallback != NULL) + { + player->stopCallback(); + } + } +} + +void MelodyPlayer::playAsync() +{ + if (melodyState == nullptr) + { + return; + } + + turnOn(); + state = State::PLAY; + + // Start immediately +#ifdef ESP32 + ticker.once(0, changeTone, this); +#else + ticker.once_scheduled(0, std::bind(changeTone, this)); +#endif +} + +void MelodyPlayer::playAsync(Melody &melody, bool loopMelody, void (*callback)(void)) +{ + if (!melody) + { + return; + } + melodyState = make_unique(melody); + loop = loopMelody; + stopCallback = callback; + playAsync(); +} + +void MelodyPlayer::stop() +{ + if (melodyState == nullptr) + { + return; + } + + haltPlay(); + state = State::STOP; + melodyState->reset(); +} + +void MelodyPlayer::pause() +{ + if (melodyState == nullptr) + { + return; + } + + haltPlay(); + state = State::PAUSE; + melodyState->saveRemainingNoteDuration(supportSemiNote); +} + +void MelodyPlayer::transferMelodyTo(MelodyPlayer &destPlayer) +{ + if (melodyState == nullptr) + { + return; + } + + destPlayer.stop(); + + bool playing = isPlaying(); + + haltPlay(); + state = State::STOP; + melodyState->saveRemainingNoteDuration(supportSemiNote); + destPlayer.melodyState = std::move(melodyState); + + if (playing) + { + destPlayer.playAsync(); + } + else + { + destPlayer.state = state; + } +} + +void MelodyPlayer::duplicateMelodyTo(MelodyPlayer &destPlayer) +{ + if (melodyState == nullptr) + { + return; + } + + destPlayer.stop(); + destPlayer.melodyState = make_unique(*(this->melodyState)); + destPlayer.melodyState->saveRemainingNoteDuration(supportSemiNote); + + if (isPlaying()) + { + destPlayer.playAsync(); + } + else + { + destPlayer.state = state; + } +} + +#ifdef ESP32 +MelodyPlayer::MelodyPlayer(unsigned char pin, unsigned char pwmChannel, bool offLevel) + : pin(pin), pwmChannel(pwmChannel), offLevel(offLevel), state(State::STOP), melodyState(nullptr) +{ + pinMode(pin, OUTPUT); + digitalWrite(pin, offLevel); +}; +#else +MelodyPlayer::MelodyPlayer(unsigned char pin, bool offLevel) + : pin(pin), offLevel(offLevel), state(State::STOP), melodyState(nullptr) +{ + pinMode(pin, OUTPUT); + digitalWrite(pin, offLevel); +}; +#endif + +void MelodyPlayer::haltPlay() +{ + // Stop player, but do not reset the melodyState + ticker.detach(); + turnOff(); +} + +void MelodyPlayer::turnOn() +{ +#ifdef ESP32 + const int resolution = 8; + // 2000 is a frequency, it will be changed at the first play + ledcSetup(pwmChannel, 2000, resolution); + ledcAttachPin(pin, pwmChannel); + ledcWrite(pwmChannel, volume); +#endif +} + +void MelodyPlayer::setVolume(byte newVolume) +{ + volume = newVolume / 2; +#ifdef ESP32 + if (state == State::PLAY) + { + ledcWrite(pwmChannel, volume); + } +#endif +} + +void MelodyPlayer::turnOff() +{ +#ifdef ESP32 + ledcWrite(pwmChannel, 0); + ledcDetachPin(pin); +#else + // Remember that this will set LOW output, it doesn't mean that buzzer is off (look at offLevel + // for more info). + noTone(pin); +#endif + + pinMode(pin, OUTPUT); + digitalWrite(pin, offLevel); +} + +void MelodyPlayer::mute() +{ + muted = true; +} + +void MelodyPlayer::unmute() +{ +#ifdef ESP32 + ledcAttachPin(pin, pwmChannel); +#endif + muted = false; +} + +void MelodyPlayer::changeTempo(int newTempo) +{ + if (melodyState == nullptr) + { + return; + } + melodyState->changeTempo(newTempo); } \ No newline at end of file diff --git a/lib/Melody Player/src/melody_player.h b/src/MelodyPlayer/melody_player.h similarity index 96% rename from lib/Melody Player/src/melody_player.h rename to src/MelodyPlayer/melody_player.h index d8ef01b7..e9c47372 100644 --- a/lib/Melody Player/src/melody_player.h +++ b/src/MelodyPlayer/melody_player.h @@ -1,283 +1,283 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#ifndef MELODY_PLAYER_H -#define MELODY_PLAYER_H - -#include "melody.h" -#include -#include - -class MelodyPlayer { -public: -#ifdef ESP32 - /** - * pwmChannel is optional and you have to configure it only if you play will - * simultaneous melodies. - */ - MelodyPlayer(unsigned char pin, unsigned char pwmChannel = 0, bool offLevel = HIGH); -#else - MelodyPlayer(unsigned char pin, bool offLevel = HIGH); -#endif - - /** - * Play the last melody in a synchrounus (blocking) way. - * If the melody is not valid, this call has no effect. - */ - void play(); - - /** - * Play the given melody in a synchronous (blocking) way. - * If the melody is not valid, this call has no effect. - */ - void play(Melody& melody); - - /** - * Play the last melody in asynchronous way (return immediately). - * If the melody is not valid, this call has no effect. - */ - void playAsync(); - - /** - * Play the given melody in asynchronous way (return immediately). - * If the melody is not valid, this call has no effect. - * Set loop to true if you want the melody to start over after at the end. - * A call back can be provided to be called at the end of the melody (only used if loop is false) - */ - void playAsync(Melody& melody, bool loop = false, void(*stopCallback)(void) = NULL); - - /** - * Stop the current melody. - * Then, if you will call play() or playAsync(), the melody restarts from the begin. - */ - void stop(); - - /** - * Pause the current melody. - * Then, if you will call play() or playAsync(), the melody continues from - * where it was paused. - */ - void pause(); - - /** - * Mute the sound of the current melody without stoppig it - * When unmute will be called the melody be resumed - */ - void mute(); - - /** - * Unmute the current melody - */ - void unmute(); - - /** - * Tell if playing. - */ - bool isPlaying() const { - return state == State::PLAY; - } - - /** - * Change the tempo of the current melody to the proided value - */ - void changeTempo(int newTempo); - - /** - * Set the volume (0-255 value) of the player (only works on ESP32 platform) - * The volume is changed by adjusting the duty-cycle of the pwm signal sent to the piezo - * A value of 0 will produce no sound while a value of 255 will set the duty cycle to 50%, - * wich will produce the highest possible volume for the piezo. - */ - void setVolume(byte volume); - - /** - * Move the current melody and player's state to the given destination Player. - * The source player stops and lose the reference to the actual melody (i.e. you have to call - * play(Melody) to make the source player play again). - */ - void transferMelodyTo(MelodyPlayer& destination); - - /** - * Duplicate the current melody and player's state to the given destination Player. - * Both players remains indipendent from each other (e.g. the melody can be independently - * stopped/paused/played). - */ - void duplicateMelodyTo(MelodyPlayer& destination); - -private: - unsigned char pin; - byte volume = 125; - bool loop = false; - bool muted = false; - void (*stopCallback)(void) = NULL; - -#ifdef ESP32 - unsigned char pwmChannel; -#endif - - /** - * The voltage to turn off the buzzer. - * - * NOTE: Passive buzzers have 2 states: the "rest" state (no power consumption) and the "active" - * state (high power consumption). To emit sound, it have to oscillate between these 2 states. If - * it stops in the active state, it doesn't emit sound, but it continues to consume energy, - * heating the buzzer and possibly damaging itself. - */ - bool offLevel; - - /** - * Store the playback state of a melody and provide the methods to control it. - */ - class MelodyState { - public: - MelodyState() : first(true), index(0), remainingNoteTime(0), timeUnit(0) {}; - MelodyState(const Melody& melody) - : melody(melody), first(true), silence(false), index(0), remainingNoteTime(0), timeUnit(melody.getTimeUnit()){}; - Melody melody; - - unsigned short getIndex() const { - return index; - } - - bool isSilence() const { - return silence; - } - - void changeTempo(int newTempo) { - timeUnit = (60 * 1000 * 4 / newTempo / 32); - } - - /** - * Advance the melody index by one step. If there is a pending partial note it hasn't any - * effect. - */ - void advance() { - if (first) { - first = false; - return; - } - if (remainingNoteTime != 0) { return; } - - if (melody.getAutomaticSilence()) { - if (silence) { - index++; - silence = false; - } else { - silence = true; - } - } else { - index++; - } - } - - /** - * Reset the state of the melody (i.e. a melody just loaded). - */ - void reset() { - first = true; - index = 0; - remainingNoteTime = 0; - silence = false; - } - - /** - * Save the time to finish the current note. - */ - void saveRemainingNoteDuration(unsigned long supportSemiNote) { - remainingNoteTime = supportSemiNote - millis(); - // Ignore partial reproduction if the current value is below the threshold. This is needed - // since Ticker may struggle with tight timings. - if (remainingNoteTime < 10) { remainingNoteTime = 0; } - } - - /** - * Clear the partial note duration. It should be called after reproduction of the partial note - * and it is propedeutic to advance() method. - */ - void resetRemainingNoteDuration() { - remainingNoteTime = 0; - } - - /** - * Get the remaining duration of the latest "saved" note. - */ - unsigned short getRemainingNoteDuration() const { - return remainingNoteTime; - } - - /** - * Get the current note. The duration is absolute and expressed in milliseconds. - */ - NoteDuration getCurrentComputedNote() const { - NoteDuration note = melody.getNote(getIndex()); - note.duration = timeUnit * note.duration; - // because the fixed point notation - note.duration /= 2; - return note; - } - - private: - bool first; - bool silence; - unsigned short index; - unsigned short timeUnit; - - /** - * Variable to support precise pauses and move/duplicate melodies between Players. - * Value are expressed in milliseconds. - */ - unsigned short remainingNoteTime; - }; - - enum class State { STOP, PLAY, PAUSE }; - - State state; - - std::unique_ptr melodyState; - - unsigned long supportSemiNote; - - Ticker ticker; - - const static bool debug = false; - - /** - * Change the current note with the next one. - */ - friend void changeTone(MelodyPlayer* melody); - - /** - * Halt the advancement of the melody reproduction. - * This is the shared method for pause and stop. - */ - void haltPlay(); - - /** - * Configure pin to emit PWM. - */ - void turnOn(); - - /** - * Disable PWM and put the buzzer is low-power state. - * This calls will fails if PWM is not initialized! - */ - void turnOff(); -}; - +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#ifndef MELODY_PLAYER_H +#define MELODY_PLAYER_H + +#include "melody.h" +#include +#include + +class MelodyPlayer { +public: +#ifdef ESP32 + /** + * pwmChannel is optional and you have to configure it only if you play will + * simultaneous melodies. + */ + MelodyPlayer(unsigned char pin, unsigned char pwmChannel = 0, bool offLevel = HIGH); +#else + MelodyPlayer(unsigned char pin, bool offLevel = HIGH); +#endif + + /** + * Play the last melody in a synchrounus (blocking) way. + * If the melody is not valid, this call has no effect. + */ + void play(); + + /** + * Play the given melody in a synchronous (blocking) way. + * If the melody is not valid, this call has no effect. + */ + void play(Melody& melody); + + /** + * Play the last melody in asynchronous way (return immediately). + * If the melody is not valid, this call has no effect. + */ + void playAsync(); + + /** + * Play the given melody in asynchronous way (return immediately). + * If the melody is not valid, this call has no effect. + * Set loop to true if you want the melody to start over after at the end. + * A call back can be provided to be called at the end of the melody (only used if loop is false) + */ + void playAsync(Melody& melody, bool loop = false, void(*stopCallback)(void) = NULL); + + /** + * Stop the current melody. + * Then, if you will call play() or playAsync(), the melody restarts from the begin. + */ + void stop(); + + /** + * Pause the current melody. + * Then, if you will call play() or playAsync(), the melody continues from + * where it was paused. + */ + void pause(); + + /** + * Mute the sound of the current melody without stoppig it + * When unmute will be called the melody be resumed + */ + void mute(); + + /** + * Unmute the current melody + */ + void unmute(); + + /** + * Tell if playing. + */ + bool isPlaying() const { + return state == State::PLAY; + } + + /** + * Change the tempo of the current melody to the proided value + */ + void changeTempo(int newTempo); + + /** + * Set the volume (0-255 value) of the player (only works on ESP32 platform) + * The volume is changed by adjusting the duty-cycle of the pwm signal sent to the piezo + * A value of 0 will produce no sound while a value of 255 will set the duty cycle to 50%, + * wich will produce the highest possible volume for the piezo. + */ + void setVolume(byte volume); + + /** + * Move the current melody and player's state to the given destination Player. + * The source player stops and lose the reference to the actual melody (i.e. you have to call + * play(Melody) to make the source player play again). + */ + void transferMelodyTo(MelodyPlayer& destination); + + /** + * Duplicate the current melody and player's state to the given destination Player. + * Both players remains indipendent from each other (e.g. the melody can be independently + * stopped/paused/played). + */ + void duplicateMelodyTo(MelodyPlayer& destination); + +private: + unsigned char pin; + byte volume = 125; + bool loop = false; + bool muted = false; + void (*stopCallback)(void) = NULL; + +#ifdef ESP32 + unsigned char pwmChannel; +#endif + + /** + * The voltage to turn off the buzzer. + * + * NOTE: Passive buzzers have 2 states: the "rest" state (no power consumption) and the "active" + * state (high power consumption). To emit sound, it have to oscillate between these 2 states. If + * it stops in the active state, it doesn't emit sound, but it continues to consume energy, + * heating the buzzer and possibly damaging itself. + */ + bool offLevel; + + /** + * Store the playback state of a melody and provide the methods to control it. + */ + class MelodyState { + public: + MelodyState() : first(true), index(0), remainingNoteTime(0), timeUnit(0) {}; + MelodyState(const Melody& melody) + : melody(melody), first(true), silence(false), index(0), remainingNoteTime(0), timeUnit(melody.getTimeUnit()){}; + Melody melody; + + unsigned short getIndex() const { + return index; + } + + bool isSilence() const { + return silence; + } + + void changeTempo(int newTempo) { + timeUnit = (60 * 1000 * 4 / newTempo / 32); + } + + /** + * Advance the melody index by one step. If there is a pending partial note it hasn't any + * effect. + */ + void advance() { + if (first) { + first = false; + return; + } + if (remainingNoteTime != 0) { return; } + + if (melody.getAutomaticSilence()) { + if (silence) { + index++; + silence = false; + } else { + silence = true; + } + } else { + index++; + } + } + + /** + * Reset the state of the melody (i.e. a melody just loaded). + */ + void reset() { + first = true; + index = 0; + remainingNoteTime = 0; + silence = false; + } + + /** + * Save the time to finish the current note. + */ + void saveRemainingNoteDuration(unsigned long supportSemiNote) { + remainingNoteTime = supportSemiNote - millis(); + // Ignore partial reproduction if the current value is below the threshold. This is needed + // since Ticker may struggle with tight timings. + if (remainingNoteTime < 10) { remainingNoteTime = 0; } + } + + /** + * Clear the partial note duration. It should be called after reproduction of the partial note + * and it is propedeutic to advance() method. + */ + void resetRemainingNoteDuration() { + remainingNoteTime = 0; + } + + /** + * Get the remaining duration of the latest "saved" note. + */ + unsigned short getRemainingNoteDuration() const { + return remainingNoteTime; + } + + /** + * Get the current note. The duration is absolute and expressed in milliseconds. + */ + NoteDuration getCurrentComputedNote() const { + NoteDuration note = melody.getNote(getIndex()); + note.duration = timeUnit * note.duration; + // because the fixed point notation + note.duration /= 2; + return note; + } + + private: + bool first; + bool silence; + unsigned short index; + unsigned short timeUnit; + + /** + * Variable to support precise pauses and move/duplicate melodies between Players. + * Value are expressed in milliseconds. + */ + unsigned short remainingNoteTime; + }; + + enum class State { STOP, PLAY, PAUSE }; + + State state; + + std::unique_ptr melodyState; + + unsigned long supportSemiNote; + + Ticker ticker; + + const static bool debug = false; + + /** + * Change the current note with the next one. + */ + friend void changeTone(MelodyPlayer* melody); + + /** + * Halt the advancement of the melody reproduction. + * This is the shared method for pause and stop. + */ + void haltPlay(); + + /** + * Configure pin to emit PWM. + */ + void turnOn(); + + /** + * Disable PWM and put the buzzer is low-power state. + * This calls will fails if PWM is not initialized! + */ + void turnOff(); +}; + #endif // END MELODY_PLAYER_H \ No newline at end of file diff --git a/lib/Melody Player/src/notes.h b/src/MelodyPlayer/notes.h similarity index 95% rename from lib/Melody Player/src/notes.h rename to src/MelodyPlayer/notes.h index 311807ec..6e93a63e 100644 --- a/lib/Melody Player/src/notes.h +++ b/src/MelodyPlayer/notes.h @@ -1,89 +1,89 @@ -#define NOTE_B0 31 -#define NOTE_C1 33 -#define NOTE_CS1 35 -#define NOTE_D1 37 -#define NOTE_DS1 39 -#define NOTE_E1 41 -#define NOTE_F1 44 -#define NOTE_FS1 46 -#define NOTE_G1 49 -#define NOTE_GS1 52 -#define NOTE_A1 55 -#define NOTE_AS1 58 -#define NOTE_B1 62 -#define NOTE_C2 65 -#define NOTE_CS2 69 -#define NOTE_D2 73 -#define NOTE_DS2 78 -#define NOTE_E2 82 -#define NOTE_F2 87 -#define NOTE_FS2 93 -#define NOTE_G2 98 -#define NOTE_GS2 104 -#define NOTE_A2 110 -#define NOTE_AS2 117 -#define NOTE_B2 123 -#define NOTE_C3 131 -#define NOTE_CS3 139 -#define NOTE_D3 147 -#define NOTE_DS3 156 -#define NOTE_E3 165 -#define NOTE_F3 175 -#define NOTE_FS3 185 -#define NOTE_G3 196 -#define NOTE_GS3 208 -#define NOTE_A3 220 -#define NOTE_AS3 233 -#define NOTE_B3 247 -#define NOTE_C4 262 -#define NOTE_CS4 277 -#define NOTE_D4 294 -#define NOTE_DS4 311 -#define NOTE_E4 330 -#define NOTE_F4 349 -#define NOTE_FS4 370 -#define NOTE_G4 392 -#define NOTE_GS4 415 -#define NOTE_A4 440 -#define NOTE_AS4 466 -#define NOTE_B4 494 -#define NOTE_C5 523 -#define NOTE_CS5 554 -#define NOTE_D5 587 -#define NOTE_DS5 622 -#define NOTE_E5 659 -#define NOTE_F5 698 -#define NOTE_FS5 740 -#define NOTE_G5 784 -#define NOTE_GS5 831 -#define NOTE_A5 880 -#define NOTE_AS5 932 -#define NOTE_B5 988 -#define NOTE_C6 1047 -#define NOTE_CS6 1109 -#define NOTE_D6 1175 -#define NOTE_DS6 1245 -#define NOTE_E6 1319 -#define NOTE_F6 1397 -#define NOTE_FS6 1480 -#define NOTE_G6 1568 -#define NOTE_GS6 1661 -#define NOTE_A6 1760 -#define NOTE_AS6 1865 -#define NOTE_B6 1976 -#define NOTE_C7 2093 -#define NOTE_CS7 2217 -#define NOTE_D7 2349 -#define NOTE_DS7 2489 -#define NOTE_E7 2637 -#define NOTE_F7 2794 -#define NOTE_FS7 2960 -#define NOTE_G7 3136 -#define NOTE_GS7 3322 -#define NOTE_A7 3520 -#define NOTE_AS7 3729 -#define NOTE_B7 3951 -#define NOTE_C8 4186 -#define NOTE_CS8 4435 -#define NOTE_D8 4699 -#define NOTE_DS8 4978 +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 diff --git a/lib/Melody Player/src/notes_array.h b/src/MelodyPlayer/notes_array.h similarity index 96% rename from lib/Melody Player/src/notes_array.h rename to src/MelodyPlayer/notes_array.h index 70a84707..0f3df2e8 100644 --- a/lib/Melody Player/src/notes_array.h +++ b/src/MelodyPlayer/notes_array.h @@ -1,147 +1,147 @@ -/*************************************************************************** - * This file is part of Melody Player, a library for Arduino * - * to play notes on piezoelectric buzzers. * - * * - * Copyright (C) 2020-2022 Fabiano Riccardi * - * * - * This library is free software; you can redistribute * - * it and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - * * - * This library 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 * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this library; if not, see * - ***************************************************************************/ -#ifndef PITCHES_UNORDERED_MAP_H -#define PITCHES_UNORDERED_MAP_H - -#include - -/** - * This class resembles the std::string_view class introduced with c++17. MelodyPlayer uses this - * version to ensure retro-compatibility with esp8266-core v2.x.x, which uses GCC v4.8.2. - */ -struct StringView { - StringView() = delete; - constexpr StringView(const char* s) : str(s), lenght(__builtin_strlen(s)) {} - - constexpr bool operator==(const StringView& other) const { - return lenght == other.lenght && (__builtin_memcmp(str, other.str, lenght) == 0); - } - - constexpr size_t length() const { - return lenght; - } - - constexpr const char* data() const { - return str; - } - -private: - const char* str; - size_t lenght; -}; - -// clang-format off -constexpr std::array, 92> noteMapping{{ - {"SILENCE", 0 }, - { "B0", 31 }, - { "C1", 33 }, - { "CS1", 35 }, - { "D1", 37 }, - { "DS1", 39 }, - { "D1", 37 }, - { "DS1", 39 }, - { "E1", 41 }, - { "F1", 44 }, - { "FS1", 46 }, - { "G1", 49 }, - { "GS1", 52 }, - { "A1", 55 }, - { "AS1", 58 }, - { "B1", 62 }, - { "C2", 65 }, - { "CS2", 69 }, - { "D2", 73 }, - { "DS2", 78 }, - { "E2", 82 }, - { "F2", 87 }, - { "FS2", 93 }, - { "G2", 98 }, - { "GS2", 104 }, - { "A2", 110 }, - { "AS2", 117 }, - { "B2", 123 }, - { "C3", 131 }, - { "CS3", 139 }, - { "D3", 147 }, - { "DS3", 156 }, - { "E3", 165 }, - { "F3", 175 }, - { "FS3", 185 }, - { "G3", 196 }, - { "GS3", 208 }, - { "A3", 220 }, - { "AS3", 233 }, - { "B3", 247 }, - { "C4", 262 }, - { "CS4", 277 }, - { "D4", 294 }, - { "DS4", 311 }, - { "E4", 330 }, - { "F4", 349 }, - { "FS4", 370 }, - { "G4", 392 }, - { "GS4", 415 }, - { "A4", 440 }, - { "AS4", 466 }, - { "B4", 494 }, - { "C5", 523 }, - { "CS5", 554 }, - { "D5", 587 }, - { "DS5", 622 }, - { "E5", 659 }, - { "F5", 698 }, - { "FS5", 740 }, - { "G5", 784 }, - { "GS5", 831 }, - { "A5", 880 }, - { "AS5", 932 }, - { "B5", 988 }, - { "C6", 1047}, - { "CS6", 1109}, - { "D6", 1175}, - { "DS6", 1245}, - { "E6", 1319}, - { "F6", 1397}, - { "FS6", 1480}, - { "G6", 1568}, - { "GS6", 1661}, - { "A6", 1760}, - { "AS6", 1865}, - { "B6", 1976}, - { "C7", 2093}, - { "CS7", 2217}, - { "D7", 2349}, - { "DS7", 2489}, - { "E7", 2637}, - { "F7", 2794}, - { "FS7", 2960}, - { "G7", 3136}, - { "GS7", 3322}, - { "A7", 3520}, - { "AS7", 3729}, - { "B7", 3951}, - { "C8", 4186}, - { "CS8", 4435}, - { "D8", 4699}, - { "DS8", 4978} -}}; -// clang-format on - -#endif // END PITCHES_UNORDERED_MAP_H +/*************************************************************************** + * This file is part of Melody Player, a library for Arduino * + * to play notes on piezoelectric buzzers. * + * * + * Copyright (C) 2020-2022 Fabiano Riccardi * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, see * + ***************************************************************************/ +#ifndef PITCHES_UNORDERED_MAP_H +#define PITCHES_UNORDERED_MAP_H + +#include + +/** + * This class resembles the std::string_view class introduced with c++17. MelodyPlayer uses this + * version to ensure retro-compatibility with esp8266-core v2.x.x, which uses GCC v4.8.2. + */ +struct StringView { + StringView() = delete; + constexpr StringView(const char* s) : str(s), lenght(__builtin_strlen(s)) {} + + constexpr bool operator==(const StringView& other) const { + return lenght == other.lenght && (__builtin_memcmp(str, other.str, lenght) == 0); + } + + constexpr size_t length() const { + return lenght; + } + + constexpr const char* data() const { + return str; + } + +private: + const char* str; + size_t lenght; +}; + +// clang-format off +constexpr std::array, 92> noteMapping{{ + {"SILENCE", 0 }, + { "B0", 31 }, + { "C1", 33 }, + { "CS1", 35 }, + { "D1", 37 }, + { "DS1", 39 }, + { "D1", 37 }, + { "DS1", 39 }, + { "E1", 41 }, + { "F1", 44 }, + { "FS1", 46 }, + { "G1", 49 }, + { "GS1", 52 }, + { "A1", 55 }, + { "AS1", 58 }, + { "B1", 62 }, + { "C2", 65 }, + { "CS2", 69 }, + { "D2", 73 }, + { "DS2", 78 }, + { "E2", 82 }, + { "F2", 87 }, + { "FS2", 93 }, + { "G2", 98 }, + { "GS2", 104 }, + { "A2", 110 }, + { "AS2", 117 }, + { "B2", 123 }, + { "C3", 131 }, + { "CS3", 139 }, + { "D3", 147 }, + { "DS3", 156 }, + { "E3", 165 }, + { "F3", 175 }, + { "FS3", 185 }, + { "G3", 196 }, + { "GS3", 208 }, + { "A3", 220 }, + { "AS3", 233 }, + { "B3", 247 }, + { "C4", 262 }, + { "CS4", 277 }, + { "D4", 294 }, + { "DS4", 311 }, + { "E4", 330 }, + { "F4", 349 }, + { "FS4", 370 }, + { "G4", 392 }, + { "GS4", 415 }, + { "A4", 440 }, + { "AS4", 466 }, + { "B4", 494 }, + { "C5", 523 }, + { "CS5", 554 }, + { "D5", 587 }, + { "DS5", 622 }, + { "E5", 659 }, + { "F5", 698 }, + { "FS5", 740 }, + { "G5", 784 }, + { "GS5", 831 }, + { "A5", 880 }, + { "AS5", 932 }, + { "B5", 988 }, + { "C6", 1047}, + { "CS6", 1109}, + { "D6", 1175}, + { "DS6", 1245}, + { "E6", 1319}, + { "F6", 1397}, + { "FS6", 1480}, + { "G6", 1568}, + { "GS6", 1661}, + { "A6", 1760}, + { "AS6", 1865}, + { "B6", 1976}, + { "C7", 2093}, + { "CS7", 2217}, + { "D7", 2349}, + { "DS7", 2489}, + { "E7", 2637}, + { "F7", 2794}, + { "FS7", 2960}, + { "G7", 3136}, + { "GS7", 3322}, + { "A7", 3520}, + { "AS7", 3729}, + { "B7", 3951}, + { "C8", 4186}, + { "CS8", 4435}, + { "D8", 4699}, + { "DS8", 4978} +}}; +// clang-format on + +#endif // END PITCHES_UNORDERED_MAP_H diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 12ca9486..c337aad3 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -199,7 +199,15 @@ String MenuManager_::menutext() } break; case VolumeMenu: - return String(SOUND_VOLUME); + if (!(DFPLAYER_ACTIVE || BUZ_VOL)) + { + return "N/A"; + } + else + { + return String(SOUND_VOLUME); + } + default: break; } @@ -254,6 +262,8 @@ void MenuManager_::rightButton() IS_CELSIUS = !IS_CELSIUS; break; case VolumeMenu: + if (!(DFPLAYER_ACTIVE || BUZ_VOL)) + break; if ((SOUND_VOLUME + 1) > 30) SOUND_VOLUME = 0; else @@ -314,6 +324,8 @@ void MenuManager_::leftButton() SOUND_ACTIVE = !SOUND_ACTIVE; break; case VolumeMenu: + if (!(DFPLAYER_ACTIVE || BUZ_VOL)) + break; if ((SOUND_VOLUME - 1) < 0) SOUND_VOLUME = 30; else diff --git a/src/PeripheryManager.cpp b/src/PeripheryManager.cpp index 5a9ad40a..d70b122c 100644 --- a/src/PeripheryManager.cpp +++ b/src/PeripheryManager.cpp @@ -5,8 +5,8 @@ #include "Adafruit_HTU21DF.h" #include "SoftwareSerial.h" #include -#include -#include +#include +#include #include "Globals.h" #include "DisplayManager.h" #include "MQTTManager.h" diff --git a/src/ServerManager.cpp b/src/ServerManager.cpp index a39c97c2..a1691d62 100644 --- a/src/ServerManager.cpp +++ b/src/ServerManager.cpp @@ -259,7 +259,7 @@ void ServerManager_::setup() MDNS.addService("awtrix", "tcp", 80); MDNS.addServiceTxt("awtrix", "tcp", "id", uniqueID); MDNS.addServiceTxt("awtrix", "tcp", "name", HOSTNAME.c_str()); - MDNS.addServiceTxt("awtrix", "tcp", "type", "awtrix_light"); + MDNS.addServiceTxt("awtrix", "tcp", "type", "awtrix3"); } configTzTime(NTP_TZ.c_str(), NTP_SERVER.c_str());