diff --git a/docs/source/Plugin/P000_Buzzer_RTTTL.rst b/docs/source/Plugin/P000_Buzzer_RTTTL.rst index 11c0d77dad..e3c7dd127f 100644 --- a/docs/source/Plugin/P000_Buzzer_RTTTL.rst +++ b/docs/source/Plugin/P000_Buzzer_RTTTL.rst @@ -57,7 +57,7 @@ From ESP Easy v2.0.0 its possible to play melodies via `RTTTL /control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 + http:///control?cmd=rtttl,14,:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 This plays a melody on pin 14. @@ -68,7 +68,7 @@ To make a boot-sound on startup, create a rule like this: .. code-block:: none On System#Boot do - rtttl,14:d=10,o=6,b=180,c,e,g + rtttl,14,:d=10,o=6,b=180,c,e,g endon `A place to find more tunes `_, all saved as txt files. diff --git a/docs/source/Plugin/P000_Piezo_element_RTTTL.rst b/docs/source/Plugin/P000_Piezo_element_RTTTL.rst index cd28215a05..59d2f49692 100644 --- a/docs/source/Plugin/P000_Piezo_element_RTTTL.rst +++ b/docs/source/Plugin/P000_Piezo_element_RTTTL.rst @@ -54,7 +54,7 @@ From ESP Easy v2.0.0 its possible to play melodies via `RTTTL /control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 + http:///control?cmd=rtttl,14,:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 This plays a melody on pin 14. @@ -65,7 +65,7 @@ To make a boot-sound on startup, create a rule like this: .. code-block:: none On System#Boot do - rtttl,14:d=10,o=6,b=180,c,e,g + rtttl,14,:d=10,o=6,b=180,c,e,g endon `A place to find more tunes `_, all saved as txt files. diff --git a/docs/source/Plugin/P000_Speaker_RTTTL.rst b/docs/source/Plugin/P000_Speaker_RTTTL.rst index 67c860fda1..ecaccd9005 100644 --- a/docs/source/Plugin/P000_Speaker_RTTTL.rst +++ b/docs/source/Plugin/P000_Speaker_RTTTL.rst @@ -54,7 +54,7 @@ From ESP Easy v2.0.0 its possible to play melodies via `RTTTL /control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 + http:///control?cmd=rtttl,14,:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 This plays a melody on pin 14. @@ -65,7 +65,7 @@ To make a boot-sound on startup, create a rule like this: .. code-block:: none On System#Boot do - rtttl,14:d=10,o=6,b=180,c,e,g + rtttl,14,:d=10,o=6,b=180,c,e,g endon `A place to find more tunes `_, all saved as txt files. diff --git a/docs/source/Plugin/P001_commands_RTTTL.repl b/docs/source/Plugin/P001_commands_RTTTL.repl index 6fc613a9e2..7b2e0ef9d9 100644 --- a/docs/source/Plugin/P001_commands_RTTTL.repl +++ b/docs/source/Plugin/P001_commands_RTTTL.repl @@ -43,4 +43,10 @@ Supported hardware: |P000_usedby_RTTTL| (Ringtones etc.) ``rtttl,14,test:d=8,o=5,b=180,c6,b,c6,p,g,g-`` `More RTTTL Ringtone Downloads `_ + + Since 2023-09-15: + + The ``rtttl`` command, by default, now uses the `AnyRtttl library `_, allowing asynchronous handling of the command. This has the advantage of being non-blocking, allowing ESPEasy to handle other tasks while the tune is being played, but comes with the disadvantage of the output possibly being interrupted/delayed by other (possibly blocking) tasks and events, so the music may not play continuously. + + Also, the previous implementation may not have stopped sound correctly after playing a song, but as this has been corrected, so there is no longer a need to turn off the GPIO after playing a song. " diff --git a/lib/AnyRtttl/AUTHORS b/lib/AnyRtttl/AUTHORS new file mode 100644 index 0000000000..ef27329702 --- /dev/null +++ b/lib/AnyRtttl/AUTHORS @@ -0,0 +1,5 @@ +# This file contains a list of people who've made contribution to +# the project. People who commit code are encouraged to add +# their names here. Please keep the list sorted by first names. + +Antoine Beauchamp diff --git a/lib/AnyRtttl/CHANGES b/lib/AnyRtttl/CHANGES new file mode 100644 index 0000000000..491a02e4d1 --- /dev/null +++ b/lib/AnyRtttl/CHANGES @@ -0,0 +1,32 @@ +Changes for 2.3 + +* Fixed issue #2 - Support for PROGMEM / FLASH melodies in non-blocking mode. + + +Changes for 2.2.1: + +* Fixed issue #4: error compiling example code. +* Fixed issue #5: Refactor build process to use Arduino CLI instead of Arduino IDE. + + +Changes for 2.2.0: + +* New feature: Build option `ANYRTTTL_BUILD_EXAMPLES` to enable/disable building AnyRtttl examples. +* New feature: Changed file/folder structure to be compatible with Arduino Library Manager. +* New feature: Using RapidAssist 0.5.0 and win32Arduino 2.3.1. + + +Changes for 2.1.229: + +* New feature: Implemented support for RTTTL in Program Memory (PROGMEM). + + +Changes for 2.0.179: + +* Library converted to AnyRtttl. +* First github release. +* Code originally release at http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/ + + +Changes for 1.0.0: +* Initial release of NonBlockingRtttl. diff --git a/lib/AnyRtttl/CMakeLists.txt b/lib/AnyRtttl/CMakeLists.txt new file mode 100644 index 0000000000..1e51d78965 --- /dev/null +++ b/lib/AnyRtttl/CMakeLists.txt @@ -0,0 +1,160 @@ +cmake_minimum_required(VERSION 3.4.3) +project(AnyRtttl) + +# Set the output folder where your program will be created +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +set( LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +############################################################################################################################################## +# Functions +############################################################################################################################################## +function(GIT_EXTERNAL DIR REPO_URL TAG) + # Find the name of the repository + get_filename_component(REPO_NAME ${REPO_URL} NAME_WE) + + # Compute output directory + set(REPO_DIR "${DIR}/${REPO_NAME}") + + if (EXISTS "${REPO_DIR}") + message(STATUS "Repository ${REPO_NAME} already exists in directory ${DIR}. Skipping git clone command.") + return() + endif() + + # Clone the repository + message(STATUS "git clone ${REPO_URL} ${REPO_DIR}") + execute_process( + COMMAND "${GIT_EXECUTABLE}" clone ${REPO_URL} ${REPO_DIR} + RESULT_VARIABLE returncode ERROR_VARIABLE error + WORKING_DIRECTORY "${DIR}") + if(returncode) + message(FATAL_ERROR "Clone failed: ${error}\n") + endif() + message(STATUS "git clone completed") + + # Checking out the required tag + message(STATUS "git checkout ${TAG}") + execute_process( + COMMAND "${GIT_EXECUTABLE}" checkout ${TAG} + RESULT_VARIABLE returncode ERROR_VARIABLE error + WORKING_DIRECTORY "${REPO_DIR}") + if(returncode) + message(FATAL_ERROR "Checkout failed: ${error}\n") + endif() + message(STATUS "git checkout completed") + + # Delete the .git folder to simulate an export of the repository + message(STATUS "Deleting ${REPO_DIR}/.git") + file(REMOVE_RECURSE "${REPO_DIR}/.git") + +endfunction() + +function(add_example name) + # Create custom example.cpp file which includes the ino sketch file. + SET(SOURCE_INO_FILE "${PROJECT_SOURCE_DIR}/examples/${name}/${name}.ino") + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/examples.cpp.in" "${PROJECT_BINARY_DIR}/${name}/examples.cpp") + + add_executable(${name} + ${ARDUINO_LIBRARY_SOURCE_FILES} + ${SOURCE_INO_FILE} + "${PROJECT_BINARY_DIR}/${name}/examples.cpp" + ) + + target_include_directories(${name} PRIVATE ${PROJECT_SOURCE_DIR}/src ${BITREADER_SOURCE_DIR} win32arduino ) + target_link_libraries(${name} PRIVATE win32arduino rapidassist) + + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + set_target_properties(${name} PROPERTIES FOLDER "examples") + + if(WIN32) + # 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\ostream(743,1): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc + # 1>D:\dev\AnyRtttl\master\third_parties\win32Arduino\install\include\win32arduino-2.4.0\SerialPrinter.h(202): message : see reference to function template instantiation 'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)' being compiled + set_target_properties(${name} PROPERTIES COMPILE_FLAGS "/wd4530") + endif() + +endfunction() + +############################################################################################################################################## +# Dependencies +############################################################################################################################################## +find_package(GTest REQUIRED) +find_package(rapidassist 0.5.0 REQUIRED) +find_package(win32arduino 2.3.1 REQUIRED) +find_package(Git REQUIRED) + +# Arduino BitReader library dependency +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external") +GIT_EXTERNAL("${CMAKE_CURRENT_SOURCE_DIR}/external" "http://github.com/end2endzone/BitReader.git" "1.3.0") +set(BITREADER_SOURCE_DIR "${PROJECT_SOURCE_DIR}/external/BitReader/src") + +############################################################################################################################################## +# Project settings +############################################################################################################################################## + +# Build options +option(ANYRTTTL_BUILD_EXAMPLES "Build all example projects" OFF) + +# Prevents annoying warnings on MSVC +if (WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + +# Find all library source and unit test files +file( GLOB ARDUINO_LIBRARY_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp ${PROJECT_SOURCE_DIR}/src/*.h ${BITREADER_SOURCE_DIR}/*.cpp ${BITREADER_SOURCE_DIR}/*.h) +file( GLOB ARDUINO_LIBRARY_TEST_FILES ${PROJECT_SOURCE_DIR}/test/*.cpp ${PROJECT_SOURCE_DIR}/test/*.h ) + +# Create unit test executable +add_executable(anyrtttl_unittest + ${ARDUINO_LIBRARY_SOURCE_FILES} + ${ARDUINO_LIBRARY_TEST_FILES} +) + +#include directories +target_include_directories(anyrtttl_unittest + PRIVATE ${PROJECT_SOURCE_DIR}/src # Arduino Library folder + ${GTEST_INCLUDE_DIR} + ${BITREADER_SOURCE_DIR} + win32arduino +) + +# Unit test projects requires to link with pthread if also linking with gtest +if(NOT WIN32) + set(PTHREAD_LIBRARIES -pthread) +endif() + +#link libraries +target_link_libraries(anyrtttl_unittest PRIVATE win32arduino rapidassist ${PTHREAD_LIBRARIES} ${GTEST_LIBRARIES} ) + +if(WIN32) + # 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\ostream(743,1): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc + # 1>D:\dev\AnyRtttl\master\third_parties\win32Arduino\install\include\win32arduino-2.4.0\SerialPrinter.h(202): message : see reference to function template instantiation 'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)' being compiled + set_target_properties(anyrtttl_unittest PROPERTIES COMPILE_FLAGS "/wd4530") +endif() + +# Copy `expected_call_stack.log` to expected locations +configure_file(${PROJECT_SOURCE_DIR}/test/expected_call_stack.log ${PROJECT_BINARY_DIR}/expected_call_stack.log COPYONLY) +set(TEST_FILES_BINARY_DIR ${EXECUTABLE_OUTPUT_PATH}) +if(WIN32) + set(TEST_FILES_BINARY_DIR ${TEST_FILES_BINARY_DIR}/${CMAKE_CFG_INTDIR}) +endif() +add_custom_command( + TARGET anyrtttl_unittest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${PROJECT_SOURCE_DIR}/test/expected_call_stack.log + ${TEST_FILES_BINARY_DIR}/expected_call_stack.log) + +############################################################################################################################################## +# Add all samples to the project unless the user has specified otherwise. +############################################################################################################################################## +if(ANYRTTTL_BUILD_EXAMPLES) + add_example("Basic") + add_example("BlockingProgramMemoryRtttl") + add_example("BlockingRtttl") + add_example("BlockingWithNonBlocking") + add_example("NonBlockingProgramMemoryRtttl") + add_example("NonBlockingRtttl") + add_example("NonBlockingStopBeforeEnd") + add_example("Play10Bits") + add_example("Play16Bits") + add_example("Rtttl2Code") +endif() + \ No newline at end of file diff --git a/lib/AnyRtttl/INSTALL.md b/lib/AnyRtttl/INSTALL.md new file mode 100644 index 0000000000..017401126e --- /dev/null +++ b/lib/AnyRtttl/INSTALL.md @@ -0,0 +1,105 @@ +# Install # + +The library can be found, installed, or updated from the Arduino IDE using the official Arduino Library Manager (available from IDE version 1.6.2). + + +The library can be installed on the system by following the same steps as with other Arduino library. + +Refer to [Installing Additional Arduino Libraries](https://www.arduino.cc/en/Guide/Libraries) tutorial for details on how to install a third party library. + + + + +# Dependencies # + +The AnyRtttl library have no dependencies on other Arduino library. However, some examples of the library requires external dependencies. + +The following Arduino Library must be installed on the system to use the library examples: + +* [BitReader v1.3.0](https://github.com/end2endzone/BitReader/tree/1.3.0) + + + + +# Build # + +The library unit tests can be build on Windows/Linux platform to maintain the product stability and level of quality. + +This section explains how to compile and build the software and how to get a test environment ready. + + + +## Prerequisites ## + + + +### Software Requirements ### + +The following software must be installed on the system before compiling unit test source code: + +* [Google C++ Testing Framework v1.8.0](https://github.com/google/googletest/tree/release-1.8.0) +* [RapidAssist v0.5.0](https://github.com/end2endzone/RapidAssist/tree/0.5.0) +* [win32Arduino v2.3.1](https://github.com/end2endzone/win32Arduino/tree/2.3.1) +* [CMake](http://www.cmake.org/) v3.4.3 (or newer) + + + +### Linux Requirements ### + +These are the base requirements to build source code: + + * GNU-compatible Make or gmake + * POSIX-standard shell + * A C++98-standard-compliant compiler + + + +### Windows Requirements ### + +* Microsoft Visual C++ 2010 or newer + + + +## Build steps ## + +The AnyRtttl unit test uses the CMake build system to generate a platform-specific build environment. CMake reads the CMakeLists.txt files, checks for installed dependencies and then generates files for the selected build system. + +The following steps show how to build the library: + +1) Download the source code from an existing [tags](https://github.com/end2endzone/AnyRtttl/tags) and extract the content to a local directory (for example `c:\projects\AnyRtttl` or `~/dev/AnyRtttl`). + +2) Open a Command Prompt (Windows) or Terminal (Linux) and browse to the project directory. + +3) Enter the following commands to generate the project files for your build system: +``` +mkdir build +cd build +cmake .. +``` + +4) Build the source code. + +**Windows** +``` +cmake --build . --config Release +``` + +**Linux** +``` +make +``` + + + + +# Testing # + +AnyRtttl comes with unit tests which help maintaining the product stability and level of quality. + +Test are build using the Google Test v1.8.0 framework. For more information on how googletest is working, see the [google test documentation primer](https://github.com/google/googletest/blob/release-1.8.0/googletest/docs/V1_6_Primer.md). + +To run tests, open a shell prompt and browse to the `build/bin` folder and run `anyrtttl_unittest` executable. For Windows users, the executable is located in `build\bin\Release`. + +Test results are saved in junit format in file `anyrtttl_unittest.release.xml`. + +The latest test results are available at the beginning of the [README.md](README.md) file. diff --git a/lib/AnyRtttl/LICENSE b/lib/AnyRtttl/LICENSE new file mode 100644 index 0000000000..dd4add21ca --- /dev/null +++ b/lib/AnyRtttl/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Antoine Beauchamp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/AnyRtttl/README.md b/lib/AnyRtttl/README.md new file mode 100644 index 0000000000..d26d7aa41b --- /dev/null +++ b/lib/AnyRtttl/README.md @@ -0,0 +1,523 @@ +![AnyRtttl logo](https://github.com/end2endzone/AnyRtttl/raw/master/docs/AnyRtttl-splashscreen.png) + + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Github Releases](https://img.shields.io/github/release/end2endzone/AnyRtttl.svg)](https://github.com/end2endzone/AnyRtttl/releases) + + + +# AnyRtttl # + +AnyRtttl is a feature rich arduino library for playing [RTTTL](http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/#Quick_recall_of_the_RTTTL_format) melodies. The library offers much more interesting features than relying on the widely available `void play_rtttl(char *p)` function. The library supports all best RTTTL features. + +Library features: + +* Really small increase in memory & code footprint compared to the usual blocking algorithm. +* Blocking & Non-Blocking modes available. +* Support custom `tone()`, `noTone()`, `delay()` and `millis()` functions. +* Compatible with external Tone libraries. +* Supports highly compressed RTTTL binary format. +* Supports RTTTL melodies stored in Program Memory (`PROGMEM`). +* Play two monolithic melodies on two different pins using 2 piezo buzzer with the help of an external Tone library. + + + +## Status ## + +Build: + +| Service | Build | Tests | +|----|-------|-------| +| AppVeyor | [![Build status](https://img.shields.io/appveyor/ci/end2endzone/AnyRtttl/master.svg?logo=appveyor)](https://ci.appveyor.com/project/end2endzone/AnyRtttl) | [![Tests status](https://img.shields.io/appveyor/tests/end2endzone/AnyRtttl/master.svg?logo=appveyor)](https://ci.appveyor.com/project/end2endzone/AnyRtttl/branch/master/tests) | +| Windows Server 2019 | [![Build on Windows](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml/badge.svg)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml) | [![Tests on Windows](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/AnyRtttl.master.Windows.json)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_windows.yml) | +| Ubuntu 22.04 | [![Build on Linux](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml/badge.svg)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml) | [![Tests on Linux](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/AnyRtttl.master.Linux.json)](https://github.com/end2endzone/AnyRtttl/actions/workflows/build_linux.yml) | + +Statistics: + +| AppVeyor | GitHub | +|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| [![Statistics](https://buildstats.info/appveyor/chart/end2endzone/AnyRtttl)](https://ci.appveyor.com/project/end2endzone/AnyRtttl/branch/master) | [![Statistics](https://buildstats.info/github/chart/end2endzone/AnyRtttl)](https://github.com/end2endzone/AnyRtttl/actions) | + + + + +# Purpose # + +After publishing [NonBlockingRtttl](https://github.com/end2endzone/NonBlockingRTTTL) arduino library, I started using the library in more complex projects which was requiring other libraries. I quickly ran into the hell of library dependencies and library conflicts. I realized that I needed more features that could help me prototype faster. + +Other libraries available which allows you to "play" a melody in [RTTTL](http://www.end2endzone.com/anyrtttl-a-feature-rich-arduino-library-for-playing-rtttl-melodies/#Quick_recall_of_the_RTTTL_format) format suffer the same issue: they are based on blocking APIs or the RTTTL data is not optimized for space. + +AnyRtttl is different since it packs multiple RTTTL related features in a single library. It supports [blocking](https://en.wikipedia.org/wiki/Blocking_(computing)) & [non-blocking](http://en.wikipedia.org/wiki/Non-blocking_algorithm) API which makes it suitable to be used by more advanced algorithm. For instance, when using the non-blocking API, the melody can be stopped when a button is pressed. The library is also compatible with external Tone libraries and it supports highly compressed RTTTL binary formats. + + + +## Non-Blocking ## + +Most of the code that can "play" a melody on internet are build the same way: sequential calls to `tone()` and `delay()` functions using hardcoded values. This type of implementation might be good for robots but not for realtime application or projects that needs to monitor pins while the song is playing. + +With AnyRtttl non-blocking mode, your program can read/write IOs pins while playing and react on changes. Implementing a "stop" or "next song" push button is easy! + + + +## External Tone or Timer #0 libraries ## + +The AnyRtttl library is also flexible by allowing you to use the build-in arduino `tone()` and `noTone()` functions or an implementation from any external library which makes it compatible with any Tone library in the market. + +The library also supports custom `delay()` and `millis()` functions. If a project requires modification to the microcontroller's build-in Timer #0, the `millis()` function may be impacted and behave incorrectly. To maximize compatibility, one can supply a custom function which behaves like the original to prevent altering playback. + + + +## Binary RTTTL ## + +The AnyRtttl library also supports playing RTTTL data which is stored as binary data instead of text. This is actually a custom implementation of the RTTTL format. Using this format, one can achieve storing an highly compressed RTTTL melody which saves memory. + +See below for details on RTTTL binary format. + + + + +# Usage # + +The following instructions show how to use the library. + +Define `ANY_RTTTL_INFO` to enable the debugging of the library state on the serial port. + +Use `ANY_RTTTL_VERSION` to get the current version of the library. + +Note, the specified macros must be defined before including `anyrtttl.h` in your sketches. + + + +## Non-blocking mode ## + + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); +Call `anyrtttl::nonblocking::begin()` to setup AnyRtttl library in non-blocking mode. + +Then call `anyrtttl::nonblocking::play()` to update the library's state and play notes as required. + +Use `anyrtttl::done()` or `anyrtttl::nonblocking::isPlaying()` to know if the library is done playing the given song. + +Anytime, one can call `anyrtttl::nonblocking::stop()` to stop playing the current song. + +The following code shows how to use the library in non-blocking mode: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} +``` + + + +## Playing RTTTL data stored in flash (program) memory ## + +AnyRtttl also supports RTTTL melodies stored in flash or Program Memory (PROGMEM). + +The `anyrtttl::nonblocking::begin()` function supports _Program Memory_ macros such as `FPSTR()` or `F()`. + +The following code shows how to use the library with RTTTL data stored in flash (program) memory instead of SRAM: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::beginProgMem(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin_P(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, FPSTR(mario)); + else if (songIndex == 3) + anyrtttl::nonblocking::begin(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} +``` + + + +# Advanced Usage # + + + +## Custom Tone function (a.k.a. RTTTL 2 code) ## + +This example shows how custom functions can be used by the AnyRtttl library to convert an RTTTL melody to arduino code. + +First define replacement functions like the following: + +```cpp +void serialTone(byte pin, uint16_t frequency, uint32_t duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(byte pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(uint32_t duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} +``` + +Each new functions prints the function call & arguments to the serial port. + +In the `setup()` function, setup the AnyRtttl library to use the new functions: + +```cpp +//Use custom functions +anyrtttl::setToneFunction(&serialTone); +anyrtttl::setNoToneFunction(&serialNoTone); +anyrtttl::setDelayFunction(&serialDelay); +``` + +Use the `anyrtttl::blocking::play()` API for "playing" an RTTTL melody and monitor the output of the serial port to see the actual arduino code generated by the library. + +The following code shows how to use the library with custom functions: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +//******************************************************************************************************************* +// The following replacement functions prints the function call & parameters to the serial port. +//******************************************************************************************************************* +void serialTone(byte pin, uint16_t frequency, uint32_t duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(byte pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(uint32_t duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); + + //Use custom functions + anyrtttl::setToneFunction(&serialTone); + anyrtttl::setNoToneFunction(&serialNoTone); + anyrtttl::setDelayFunction(&serialDelay); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} +``` + + +## Play 16 bits per note RTTTL ## + +Note that this mode requires that an RTTTL melody be already converted to 16-bits per note binary format. + +Use the `anyrtttl::blocking::play16Bits()` API for playing an RTTTL melody encoded as 16 bits per note. + +The following code shows how to use the library with 16-bits per note binary RTTTL: + +```cpp +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 16 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris16[] = {0x0A, 0x14, 0x12, 0x02, 0x33, 0x01, 0x03, 0x02, 0x0B, 0x02, 0x14, 0x02, 0x0C, 0x02, 0x03, 0x02, 0x33, 0x01, 0x2A, 0x01, 0x2B, 0x01, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x29, 0x01, 0x3B, 0x01, 0x0A, 0x02, 0x1B, 0x02, 0x2A, 0x02, 0x23, 0x02, 0x1B, 0x02, 0x12, 0x02, 0x13, 0x02, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x2A, 0x01}; +const int tetris16_length = 42; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play16Bits(BUZZER_PIN, tetris16, tetris16_length); + + while(true) + { + } +} +``` + + + +## Play 10 bits per note RTTTL ## + +Note that this mode requires that an RTTTL melody be already converted to 10-bits per note binary format. + +Create a function that will be used by AnyRtttl library to read bits as required. The signature of the library must look like this: `uint16_t function_name(uint8_t numBits)`. + +Note that this code requires the [BitReader](https://github.com/end2endzone/BitReader) library to extract bits from the RTTTL binary buffer. The implementation of `readNextBits()` function delegates the job to the BitReader's `read()` method. + +In the `setup()` function, setup the external library that is used for reading bits: `bitreader.setBuffer(tetris10);`. + +Use the `anyrtttl::blocking::play10Bits()` API for playing an RTTTL melody encoded as 10 bits per note. The 3rd argument of the function requires a pointer to the function extracting bits: `&function_name`. + +The following code shows how to use the library with 10-bits per note binary RTTTL: + +```cpp +#include +#include +#include + +//The BitReader library is required for extracting 10 bit blocks from the RTTTL buffer. +//It can be installed from Arduino Library Manager or from https://github.com/end2endzone/BitReader/releases +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 10 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris10[] = {0x0A, 0x14, 0x12, 0xCE, 0x34, 0xE0, 0x82, 0x14, 0x32, 0x38, 0xE0, 0x4C, 0x2A, 0xAD, 0x34, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA5, 0xB4, 0x93, 0x82, 0x1B, 0xAA, 0x38, 0xE2, 0x86, 0x12, 0x4E, 0x38, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA9, 0x04}; +const int tetris10_length = 42; + +//bit reader support +#ifndef USE_BITADDRESS_READ_WRITE +BitReader bitreader; +#else +BitAddress bitreader; +#endif +uint16_t readNextBits(uint8_t numBits) +{ + uint16_t bits = 0; + bitreader.read(numBits, &bits); + return bits; +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + bitreader.setBuffer(tetris10); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play10Bits(BUZZER_PIN, tetris10_length, &readNextBits); + + while(true) + { + } +} +``` + + + +## Other ## + +More AnyRtttl examples are also available: + +* [Basic](examples/Basic/Basic.ino) +* [BlockingProgramMemoryRtttl](examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino) +* [BlockingRtttl](examples/BlockingRtttl/BlockingRtttl.ino) +* [BlockingWithNonBlocking](examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino) +* [NonBlockingProgramMemoryRtttl](examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino) +* [NonBlockingRtttl](examples/NonBlockingRtttl/NonBlockingRtttl.ino) +* [NonBlockingStopBeforeEnd](examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino) +* [Play10Bits](examples/Play10Bits/Play10Bits.ino) +* [Play16Bits](examples/Play16Bits/Play16Bits.ino) +* [Rtttl2Code](examples/Rtttl2Code/Rtttl2Code.ino) + + + + +# Binary RTTTL format definition # + +The following section defines the field order and size (in bits) required for encoding / decoding of each melody as binary RTTTL. + +This is actually a custom implementation of the RTTTL format. Using this format, one can achieve storing an highly compressed RTTTL melody which saves memory. + +Note that all fields definition are defined in LSB to MSB order. + +## Header ## + +The first 16 bits stores the RTTTL default section (a.k.a header) which is defined as the following: + +| Field name | Size (bits) | Range | Description | +|-------------------------|:-----------:|----------|-------------------------------------------------------------| +| Default duration index | 3 | [0, 7] | Matches the index used for `getNoteDurationFromIndex()` API | +| Default octave index | 2 | [0, 3] | Matches the index used for `getNoteOctaveFromIndex()` API. | +| Beats per minutes (BPM) | 10 | [1, 900] | | +| Padding | 1 | | | + +## Notes ## + +Next is each note's of the melody. Each note is encoded as 10 bits (or 16 bits) per note. Notes are defined as the following: + +| Field name | Size (bits) | Range | Description | +|--------------------|:-----------:|---------|--------------------------------------------------------------| +| Duration index | 3 | [0, 7] | Matches the index used for `getNoteDurationFromIndex()` API. | +| Note letter index | 3 | [0, 7] | Matches the index used for `getNoteLetterFromIndex()` API. | +| Pound | 1 | boolean | Defines if the note is pounded or not. | +| Dotted | 1 | boolean | Defines if the note is dotted or not. | +| Octave index | 2 | [0, 3] | Matches the index used for `getNoteOctaveFromIndex()` API. | +| Padding (optional) | 6 | | See description below. | + +The last field of a note (defined as `Padding`) is an optional 6 bits field. The AnyRtttl library supports both 10 bits per note and 16 bits per note definitions. Use the appropriate API for playing both format. + + + +## 10 bits per note (no padding) ## + +Each RTTTL note is encoded into 10 bits which is the minimum size of a note. This storage method is the best compression method for storing RTTTL melodies and reduces the usage of the dynamic memory to the minimum. + +However, since all notes are not aligned on multiple of 8 bits, addressing each note by an offset is impossible which makes the playback harder. Each notes must be deserialized one after the other from a buffer using blocks of 10 bits which increases the program storage space footprint. + +An external arduino library (or custom code) is required to allow AnyRtttl library to consume bits as needed. The arduino [BitReader](https://github.com/end2endzone/BitReader) library may be used for handling bit deserialization but any library that can extract a given number of bits from a buffer would work. + + + +## 16 bits per note (with padding) ## + +Each RTTTL note is encoded into 16 bits which is much better than the average 3.28 bytes per note text format. This storage method is optimum for storing RTTTL melodies and reduces the usage of the dynamic memory without increasing to much program storage space. + +All notes are aligned on 16 bits. Addressing each note by an offset allows an easy playback. Only the first 10 bits of each 16 bits block is used. The value of the padding field is undefined. + + + +## Playback ## + +The following AnyRtttl blocking APIs are available for playing both binary formats: +* 10 bits per note: `anyrtttl::blocking::play10Bits()`. +* 16 bits per note: `anyrtttl::blocking::play16Bits()`. + + + + +# Building # + +Please refer to file [INSTALL.md](INSTALL.md) for details on how installing/building the application. + + + + +# Platforms # + +AnyRtttl has been tested with the following platform: + + * Linux x86/x64 + * Windows x86/x64 + + + + +# Versioning # + +We use [Semantic Versioning 2.0.0](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/end2endzone/AnyRtttl/tags). + + + + +# Authors # + +* **Antoine Beauchamp** - *Initial work* - [end2endzone](https://github.com/end2endzone) + +See also the list of [contributors](https://github.com/end2endzone/AnyRtttl/blob/master/AUTHORS) who participated in this project. + + + + +# License # + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details diff --git a/lib/AnyRtttl/appveyor.yml b/lib/AnyRtttl/appveyor.yml new file mode 100644 index 0000000000..88c6ab9481 --- /dev/null +++ b/lib/AnyRtttl/appveyor.yml @@ -0,0 +1,81 @@ +#---------------------------------# +# general configuration # +#---------------------------------# + +# version format +version: "{branch} (#{build})" + +# branches to build +branches: + only: + - master + - /feature-issue.*/ + +#---------------------------------# +# environment configuration # +#---------------------------------# + +# Build worker image (VM template) +image: Visual Studio 2019 + +# scripts that are called at very beginning, before repo cloning +init: + - cmd: git config --global core.autocrlf true + - ps: $env:GIT_HASH=$env:APPVEYOR_REPO_COMMIT.Substring(0, 10) + +# clone directory +clone_folder: c:\projects\AnyRtttl + +# scripts that run after cloning repository +install: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_arduinocli.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_install_libraries.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_this.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_googletest.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_rapidassist.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\install_win32arduino.bat + +#---------------------------------# +# build configuration # +#---------------------------------# + +environment: + PlatformToolset: v142 + +# build platform, i.e. x86, x64, Any CPU. This setting is optional. +platform: Win32 + +# build Configuration, i.e. Debug, Release, etc. +configuration: Release + +build_script: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\build_library.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\external\BitReader\ci\appveyor\arduino_library_install.bat +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Basic +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingProgramMemoryRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat BlockingWithNonBlocking +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingProgramMemoryRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingRtttl +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat NonBlockingStopBeforeEnd +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Play10Bits +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Play16Bits +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\arduino_build_sketch.bat Rtttl2Code + + +#---------------------------------# +# tests configuration # +#---------------------------------# + +# to run your custom scripts instead of automatic tests +test_script: +- cmd: call %APPVEYOR_BUILD_FOLDER%\ci\appveyor\test_script.bat +- ps: . "$env:APPVEYOR_BUILD_FOLDER\ci\appveyor\UploadJUnitFiles.ps1" -Path "$env:APPVEYOR_BUILD_FOLDER\build\bin" + +#---------------------------------# +# artifacts configuration # +#---------------------------------# + +artifacts: +- path: build\bin\Release\anyrtttl_unittest.release.xml + name: anyrtttl_unittest.release.xml diff --git a/lib/AnyRtttl/docs/AnyRtttl-splashscreen.png b/lib/AnyRtttl/docs/AnyRtttl-splashscreen.png new file mode 100644 index 0000000000..55c5d76751 Binary files /dev/null and b/lib/AnyRtttl/docs/AnyRtttl-splashscreen.png differ diff --git a/lib/AnyRtttl/docs/AnyRtttl.xcf b/lib/AnyRtttl/docs/AnyRtttl.xcf new file mode 100644 index 0000000000..8f8a2b0905 Binary files /dev/null and b/lib/AnyRtttl/docs/AnyRtttl.xcf differ diff --git a/lib/AnyRtttl/examples.cpp.in b/lib/AnyRtttl/examples.cpp.in new file mode 100644 index 0000000000..adbafa79be --- /dev/null +++ b/lib/AnyRtttl/examples.cpp.in @@ -0,0 +1,14 @@ +#include +#include "Arduino.h" +#include "@SOURCE_INO_FILE@" + +using namespace testarduino; + +int main(int argc, char* argv[]) +{ + printf("Calling setup()...\n"); + setup(); + + printf("Calling loop()...\n"); + loop(); +} diff --git a/lib/AnyRtttl/examples/Basic/Basic.ino b/lib/AnyRtttl/examples/Basic/Basic.ino new file mode 100644 index 0000000000..4c7ba902d6 --- /dev/null +++ b/lib/AnyRtttl/examples/Basic/Basic.ino @@ -0,0 +1,22 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino b/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino new file mode 100644 index 0000000000..cf28266d55 --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingProgramMemoryRtttl/BlockingProgramMemoryRtttl.ino @@ -0,0 +1,37 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=140:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println("ready"); +} + +void loop() { + anyrtttl::blocking::playProgMem(BUZZER_PIN, tetris); + delay(1000); + + anyrtttl::blocking::play_P(BUZZER_PIN, arkanoid); + delay(1000); + +#if defined(ESP8266) + anyrtttl::blocking::play(BUZZER_PIN, FPSTR(mario)); + delay(1000); +#endif + + anyrtttl::blocking::play(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + delay(1000); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino b/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino new file mode 100644 index 0000000000..6156ecdd1f --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingRtttl/BlockingRtttl.ino @@ -0,0 +1,31 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=140:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println("ready"); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, arkanoid); + delay(1000); + + anyrtttl::blocking::play(BUZZER_PIN, mario); + delay(1000); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino b/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino new file mode 100644 index 0000000000..9f6e4d0643 --- /dev/null +++ b/lib/AnyRtttl/examples/BlockingWithNonBlocking/BlockingWithNonBlocking.ino @@ -0,0 +1,44 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + + // Play tetris and wait until done playing before jumping to the next song. + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + // Play arkanoid and loop until done playing before jumping to the next song. + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + // Play mario and loop until done playing before looping again. + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/ESP32Rtttl/ESP32Rtttl.ino b/lib/AnyRtttl/examples/ESP32Rtttl/ESP32Rtttl.ino new file mode 100644 index 0000000000..e5039c79fb --- /dev/null +++ b/lib/AnyRtttl/examples/ESP32Rtttl/ESP32Rtttl.ino @@ -0,0 +1,62 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=140:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) + +// tone() and noTone() are not implemented for Arduino core for the ESP32 +// See https://github.com/espressif/arduino-esp32/issues/980 +// and https://github.com/espressif/arduino-esp32/issues/1720 +void esp32NoTone(uint8_t pin) { + // don't care about the given pin + ledcWrite(0, 0); // channel, volume +} + +void esp32Tone(uint8_t pin, unsigned int frq, unsigned long duration) { + // don't care about the given pin or the duration + ledcWriteTone(0, frq); // channel, freq + ledcWrite(0, 255); // channel, volume +} + +void esp32ToneSetup(uint8_t pin) { + ledcSetup(0, 1000, 10); // resolution always seems to be 10bit, no matter what is given + ledcAttachPin(pin, 0); +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + // setup AnyRtttl for ESP32 + esp32ToneSetup(BUZZER_PIN); + anyrtttl::setToneFunction(&esp32Tone); + anyrtttl::setNoToneFunction(&esp32NoTone); + + Serial.begin(115200); + + Serial.println("ready"); +} + +void loop() { + anyrtttl::blocking::playProgMem(BUZZER_PIN, tetris); + delay(1000); + + anyrtttl::blocking::play_P(BUZZER_PIN, arkanoid); + delay(1000); + +#if defined(ESP8266) || defined(ESP32) + anyrtttl::blocking::play(BUZZER_PIN, FPSTR(mario)); + delay(1000); +#endif + + anyrtttl::blocking::play(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + delay(1000); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino b/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino new file mode 100644 index 0000000000..fb92e1f466 --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingProgramMemoryRtttl/NonBlockingProgramMemoryRtttl.ino @@ -0,0 +1,43 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char tetris[] PROGMEM = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char arkanoid[] PROGMEM = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char mario[] PROGMEM = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +// James Bond theme defined in inline code below (also stored in flash memory) +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::beginProgMem(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin_P(BUZZER_PIN, arkanoid); +#if defined(ESP8266) + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, FPSTR(mario)); +#endif + else if (songIndex == 3) + anyrtttl::nonblocking::begin(BUZZER_PIN, F("Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6")); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino b/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino new file mode 100644 index 0000000000..fac679bd89 --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingRtttl/NonBlockingRtttl.ino @@ -0,0 +1,38 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; +const char * arkanoid = "Arkanoid:d=4,o=5,b=140:8g6,16p,16g.6,2a#6,32p,8a6,8g6,8f6,8a6,2g6"; +const char * mario = "mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6"; +byte songIndex = 0; //which song to play when the previous one finishes + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + // If we are not playing something + if ( !anyrtttl::nonblocking::isPlaying() ) + { + // Play a song based on songIndex. + if (songIndex == 0) + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + else if (songIndex == 1) + anyrtttl::nonblocking::begin(BUZZER_PIN, arkanoid); + else if (songIndex == 2) + anyrtttl::nonblocking::begin(BUZZER_PIN, mario); + + //Set songIndex ready for next song + songIndex++; + } + else + { + anyrtttl::nonblocking::play(); + } +} diff --git a/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino b/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino new file mode 100644 index 0000000000..7cffd2c93f --- /dev/null +++ b/lib/AnyRtttl/examples/NonBlockingStopBeforeEnd/NonBlockingStopBeforeEnd.ino @@ -0,0 +1,42 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +unsigned long playStart = 0; +bool firstPass = true; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + if (firstPass) { + anyrtttl::nonblocking::begin(BUZZER_PIN, tetris); + + //remember when we started playing the song + playStart = millis(); + + firstPass = false; + } + + //if we are playing something + if ( anyrtttl::nonblocking::isPlaying() ) { + + //does the melody been playing for more than 5 seconds ? + if ( millis() - playStart > 5000 ) + { + anyrtttl::nonblocking::stop(); + } + } + + //if anything available for playing, play it + anyrtttl::nonblocking::play(); + +} diff --git a/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino b/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino new file mode 100644 index 0000000000..841ee7cc75 --- /dev/null +++ b/lib/AnyRtttl/examples/Play10Bits/Play10Bits.ino @@ -0,0 +1,44 @@ +#include +#include +#include + +//The BitReader library is required for extracting 10 bit blocks from the RTTTL buffer. +//It can be installed from Arduino Library Manager or from https://github.com/end2endzone/BitReader/releases +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 10 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris10[] = {0x0A, 0x14, 0x12, 0xCE, 0x34, 0xE0, 0x82, 0x14, 0x32, 0x38, 0xE0, 0x4C, 0x2A, 0xAD, 0x34, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA5, 0xB4, 0x93, 0x82, 0x1B, 0xAA, 0x38, 0xE2, 0x86, 0x12, 0x4E, 0x38, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA9, 0x04}; +const int tetris10_length = 42; + +//bit reader support +#ifndef USE_BITADDRESS_READ_WRITE +BitReader bitreader; +#else +BitAddress bitreader; +#endif +uint16_t readNextBits(uint8_t numBits) +{ + uint16_t bits = 0; + bitreader.read(numBits, &bits); + return bits; +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + bitreader.setBuffer(tetris10); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play10Bits(BUZZER_PIN, tetris10_length, &readNextBits); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino b/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino new file mode 100644 index 0000000000..ad18800e40 --- /dev/null +++ b/lib/AnyRtttl/examples/Play16Bits/Play16Bits.ino @@ -0,0 +1,25 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 + +//RTTTL 16 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +const unsigned char tetris16[] = {0x0A, 0x14, 0x12, 0x02, 0x33, 0x01, 0x03, 0x02, 0x0B, 0x02, 0x14, 0x02, 0x0C, 0x02, 0x03, 0x02, 0x33, 0x01, 0x2A, 0x01, 0x2B, 0x01, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x29, 0x01, 0x3B, 0x01, 0x0A, 0x02, 0x1B, 0x02, 0x2A, 0x02, 0x23, 0x02, 0x1B, 0x02, 0x12, 0x02, 0x13, 0x02, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x2A, 0x01}; +const int tetris16_length = 42; + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); +} + +void loop() { + anyrtttl::blocking::play16Bits(BUZZER_PIN, tetris16, tetris16_length); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino b/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino new file mode 100644 index 0000000000..afb3c30510 --- /dev/null +++ b/lib/AnyRtttl/examples/Rtttl2Code/Rtttl2Code.ino @@ -0,0 +1,52 @@ +#include +#include +#include + +//project's constants +#define BUZZER_PIN 8 +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +//******************************************************************************************************************* +// The following replacement functions prints the function call & parameters to the serial port. +//******************************************************************************************************************* +void serialTone(uint8_t pin, unsigned int frequency, unsigned long duration) { + Serial.print("tone("); + Serial.print(pin); + Serial.print(","); + Serial.print(frequency); + Serial.print(","); + Serial.print(duration); + Serial.println(");"); +} + +void serialNoTone(uint8_t pin) { + Serial.print("noTone("); + Serial.print(pin); + Serial.println(");"); +} + +void serialDelay(unsigned long duration) { + Serial.print("delay("); + Serial.print(duration); + Serial.println(");"); +} + +void setup() { + pinMode(BUZZER_PIN, OUTPUT); + + Serial.begin(115200); + Serial.println(); + + //Use custom functions + anyrtttl::setToneFunction(&serialTone); + anyrtttl::setNoToneFunction(&serialNoTone); + anyrtttl::setDelayFunction(&serialDelay); +} + +void loop() { + anyrtttl::blocking::play(BUZZER_PIN, tetris); + + while(true) + { + } +} diff --git a/lib/AnyRtttl/keywords.txt b/lib/AnyRtttl/keywords.txt new file mode 100644 index 0000000000..d327e0eda1 --- /dev/null +++ b/lib/AnyRtttl/keywords.txt @@ -0,0 +1,16 @@ +anyrtttl KEYWORD1 +play16Bits KEYWORD2 +play10Bits KEYWORD2 +begin KEYWORD2 +beginProgMem KEYWORD2 +begin_P KEYWORD2 +play KEYWORD2 +playProgMem KEYWORD2 +play_P KEYWORD2 +stop KEYWORD2 +isPlaying KEYWORD2 +done KEYWORD2 +setToneFunction KEYWORD2 +setNoToneFunction KEYWORD2 +setDelayFunction KEYWORD2 +setMillisFunction KEYWORD2 diff --git a/lib/AnyRtttl/library.properties b/lib/AnyRtttl/library.properties new file mode 100644 index 0000000000..f8c77a30a6 --- /dev/null +++ b/lib/AnyRtttl/library.properties @@ -0,0 +1,9 @@ +name=AnyRtttl +version=2.3 +author=Antoine Beauchamp +maintainer=Antoine Beauchamp +sentence=A feature rich arduino library for playing rtttl melodies. +paragraph=The AnyRtttl is a feature rich library which supports all best RTTTL features: Blocking & Non-Blocking modes, custom tone(), delay() and millis() functions, PROGMEM support, and much more. +category=Other +url=https://github.com/end2endzone/AnyRtttl +architectures=* diff --git a/lib/AnyRtttl/src/anyrtttl.cpp b/lib/AnyRtttl/src/anyrtttl.cpp new file mode 100644 index 0000000000..584e968795 --- /dev/null +++ b/lib/AnyRtttl/src/anyrtttl.cpp @@ -0,0 +1,672 @@ +// --------------------------------------------------------------------------- +// AUTHOR/LICENSE: +// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file. +// The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl +// MIT License: http://www.opensource.org/licenses/mit-license.php +// --------------------------------------------------------------------------- +#include "Arduino.h" +#include "anyrtttl.h" +#include "binrtttl.h" + +/********************************************************* + * RTTTL Library data + *********************************************************/ + +namespace anyrtttl +{ + +const uint16_t notes[] = { NOTE_SILENT, +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 +}; + +#define isdigit(n) (n >= '0' && n <= '9') +typedef uint16_t TONE_DURATION; +static const byte NOTES_PER_OCTAVE = 12; + +const char * buffer = ""; +ReadCharFuncPtr readCharFunc = &readChar; +int bufferIndex = -32760; +byte default_dur = 4; +byte default_oct = 5; +RTTTL_BPM bpm = 63; +RTTTL_DURATION wholenote; +byte pin = -1; +unsigned long delayToNextNote = 0; //milliseconds before playing the next note +bool playing = false; +TONE_DURATION duration; +byte noteOffset; +RTTTL_OCTAVE_VALUE scale; +int tmpNumber; + +const char * readNumber(const char * iBuffer, int & oValue, ReadCharFuncPtr iReadCharFunc) +{ + oValue = 0; + while(isdigit(iReadCharFunc(iBuffer))) + { + oValue = (oValue * 10) + (readCharFunc(iBuffer) - '0'); + iBuffer++; + } + return iBuffer; +} + +void serialPrint(const char * iBuffer, ReadCharFuncPtr iReadCharFunc) +{ + char c = readCharFunc(iBuffer); + while(c) { + Serial.print(c); + iBuffer++; + c = readCharFunc(iBuffer); + } +} + +/**************************************************************************** + * Custom functions + ****************************************************************************/ + +ToneFuncPtr _tone = &tone; +NoToneFuncPtr _noTone = &noTone; +DelayFuncPtr _delay = &delay; +MillisFuncPtr _millis = &millis; + +void setToneFunction(ToneFuncPtr iFunc) { + _tone = iFunc; +} + +void setNoToneFunction(NoToneFuncPtr iFunc) { + _noTone = iFunc; +} + +void setDelayFunction(DelayFuncPtr iFunc) { + _delay = iFunc; +} + +void setMillisFunction(MillisFuncPtr iFunc) { + _millis = iFunc; +} + +char readChar(const char * iBuffer) { + return *iBuffer; +} + +char readChar_P(const char * iBuffer) { + return pgm_read_byte_near(iBuffer); +} + + + +/**************************************************************************** + * Blocking API + ****************************************************************************/ +namespace blocking +{ + +void play(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc) { + // Absolutely no error checking in here + + default_dur = 4; + default_oct = 6; + bpm = 63; + buffer = iBuffer; + readCharFunc = iReadCharFunc; + + #ifdef ANY_RTTTL_DEBUG + Serial.print("playing: "); + serialPrint(buffer, readCharFunc); + Serial.println(); + #endif + + // format: d=N,o=N,b=NNN: + // find the start (skip name, etc) + + while(readCharFunc(buffer) != ':') buffer++; // ignore name + buffer++; // skip ':' + + // get default duration + if(readCharFunc(buffer) == 'd') + { + buffer++; buffer++; // skip "d=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + if(tmpNumber > 0) + default_dur = tmpNumber; + buffer++; // skip comma + } + + #ifdef ANY_RTTTL_INFO + Serial.print("ddur: "); Serial.println(default_dur, 10); + #endif + + // get default octave + if(readCharFunc(buffer) == 'o') + { + buffer++; buffer++; // skip "o=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + if(tmpNumber >= 3 && tmpNumber <= 7) + default_oct = tmpNumber; + buffer++; // skip comma + } + + #ifdef ANY_RTTTL_INFO + Serial.print("doct: "); Serial.println(default_oct, 10); + #endif + + // get BPM + if(readCharFunc(buffer) == 'b') + { + buffer++; buffer++; // skip "b=" + buffer = readNumber(buffer, tmpNumber, readCharFunc); + bpm = tmpNumber; + buffer++; // skip colon + } + + #ifdef ANY_RTTTL_INFO + Serial.print("bpm: "); Serial.println(bpm, 10); + #endif + + // BPM usually expresses the number of quarter notes per minute + wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds) + + #ifdef ANY_RTTTL_INFO + Serial.print("wn: "); Serial.println(wholenote, 10); + #endif + + // now begin note loop + while(readCharFunc(buffer)) + { + // first, get note duration, if available + buffer = readNumber(buffer, tmpNumber, readCharFunc); + + if(tmpNumber) + duration = wholenote / tmpNumber; + else + duration = wholenote / default_dur; // we will need to check if we are a dotted noteOffset after + + // now get the note + noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer)); + buffer++; + + // now, get optional '#' sharp + if(readCharFunc(buffer) == '#') + { + noteOffset++; + buffer++; + } + + // now, get optional '.' dotted note + if(readCharFunc(buffer) == '.') + { + duration += duration/2; + buffer++; + } + + // now, get scale + if(isdigit(readCharFunc(buffer))) + { + scale = readCharFunc(buffer) - '0'; + buffer++; + } + else + { + scale = default_oct; + } + + if(readCharFunc(buffer) == ',') + buffer++; // skip comma for next note (or we may be at the end) + + // now play the note + if(noteOffset) + { + uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset]; + + #ifdef ANY_RTTTL_INFO + Serial.print("Playing: "); + Serial.print(scale, 10); Serial.print(' '); + Serial.print(noteOffset, 10); Serial.print(" ("); + Serial.print(frequency, 10); + Serial.print(") "); + Serial.println(duration, 10); + #endif + + _tone(iPin, frequency, duration); + _delay(duration+1); + _noTone(iPin); + } + else + { + #ifdef ANY_RTTTL_INFO + Serial.print("Pausing: "); + Serial.println(duration, 10); + #endif + _delay(duration); + } + } +} + +void play(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar); } + +void play(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); } +void playProgMem(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); } +void play_P(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); } +void play_P(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); } + +void play16Bits(int iPin, const unsigned char * iBuffer, int iNumNotes) { + // Absolutely no error checking in here + + RTTTL_DEFAULT_VALUE_SECTION * defaultSection = (RTTTL_DEFAULT_VALUE_SECTION *)iBuffer; + RTTTL_NOTE * notesBuffer = (RTTTL_NOTE *)iBuffer; + + bpm = defaultSection->bpm; + + #ifdef ANY_RTTTL_DEBUG + Serial.print("numNotes="); + Serial.println(iNumNotes); + // format: d=N,o=N,b=NNN: + Serial.print("d="); + Serial.print(getNoteDurationFromIndex(defaultSection->durationIdx)); + Serial.print(",o="); + Serial.print(getNoteOctaveFromIndex(defaultSection->octaveIdx)); + Serial.print(",b="); + Serial.println(bpm); + #endif + + // BPM usually expresses the number of quarter notes per minute + wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds) + + // now begin note loop + for(int i=0; i= 0 && iIndex < gNoteLettersCount) + return gNoteLetters[iIndex]; + return -1; +} + +uint16_t getNoteLettersCount() +{ + return gNoteLettersCount; +} + +NOTE_LETTER_INDEX findNoteLetterIndex(RTTTL_NOTE_LETTER n) +{ + for(NOTE_LETTER_INDEX i=0; i= 0 && iIndex < gNoteLettersCount) + return gNoteOffsets[iIndex]; + return 0; +} + +int getNoteOffsetFromLetter(RTTTL_NOTE_LETTER n) +{ + NOTE_LETTER_INDEX index = findNoteLetterIndex(n); + return getNoteOffsetFromLetterIndex(index); +} + +RTTTL_DURATION getNoteDurationFromIndex(DURATION_INDEX iIndex) +{ + if (iIndex >= 0 && iIndex < gNoteDurationsCount) + return gNoteDurations[iIndex]; + return -1; +} + +uint16_t getNoteDurationsCount() +{ + return gNoteDurationsCount; +} + +DURATION_INDEX findNoteDurationIndex(RTTTL_DURATION n) +{ + for(DURATION_INDEX i=0; i= 0 && iIndex < gNoteOctavesCount) + return gNoteOctaves[iIndex]; + return -1; +} + +uint16_t getNoteOctavesCount() +{ + return gNoteOctavesCount; +} + +OCTAVE_INDEX findNoteOctaveIndex(RTTTL_OCTAVE_VALUE n) +{ + for(OCTAVE_INDEX i=0; i= 0 && iIndex < gNoteBpmsCount) + return gNoteBpms[iIndex]; + return -1; +} + +uint16_t getBpmsCount() +{ + return gNoteBpmsCount; +} + +BPM_INDEX findBpmIndex(RTTTL_BPM n) +{ + for(BPM_INDEX i=0; i +#include + +#include "TestAnyRtttl.h" +#include "Arduino.h" +#include "anyrtttl.h" +#include "bitreader.h" + +#include "rapidassist/strings.h" +#include "rapidassist/testing.h" +#include "rapidassist/filesystem.h" + +#include "IncrementalClockStrategy.h" + +using namespace testarduino; + +//rtttl native format +const char * tetris = "tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; + +//RTTTL 10 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +//Compatible with AnyRtttl library v2.0 +//The code & updates for the AnyRtttl library can be found on http://end2endzone.com +const unsigned char tetris10[] = {0x0A, 0x14, 0x12, 0xCE, 0x34, 0xE0, 0x82, 0x14, 0x32, 0x38, 0xE0, 0x4C, 0x2A, 0xAD, 0x34, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA5, 0xB4, 0x93, 0x82, 0x1B, 0xAA, 0x38, 0xE2, 0x86, 0x12, 0x4E, 0x38, 0xA0, 0x84, 0x0B, 0x0E, 0x28, 0xD3, 0x4C, 0x03, 0x2A, 0x28, 0xA1, 0x80, 0x2A, 0xA9, 0x04}; +const int tetris10_length = 42; + +//RTTTL 16 bits binary format for the following: tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a +//Compatible with AnyRtttl library v2.0 +//The code & updates for the AnyRtttl library can be found on http://end2endzone.com +const unsigned char tetris16[] = {0x0A, 0x14, 0x12, 0x02, 0x33, 0x01, 0x03, 0x02, 0x0B, 0x02, 0x14, 0x02, 0x0C, 0x02, 0x03, 0x02, 0x33, 0x01, 0x2A, 0x01, 0x2B, 0x01, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x29, 0x01, 0x3B, 0x01, 0x0A, 0x02, 0x1B, 0x02, 0x2A, 0x02, 0x23, 0x02, 0x1B, 0x02, 0x12, 0x02, 0x13, 0x02, 0x03, 0x02, 0x12, 0x02, 0x0B, 0x02, 0x03, 0x02, 0x32, 0x01, 0x33, 0x01, 0x03, 0x02, 0x0A, 0x02, 0x12, 0x02, 0x02, 0x02, 0x2A, 0x01, 0x2A, 0x01}; +const int tetris16_length = 42; + + +#define PIEZO_PIN 99 + +namespace arduino { namespace test +{ + + //bit reader support + #ifndef USE_BITADDRESS_READ_WRITE + BitReader bitreader; + #else + BitAddress bitreader; + #endif + uint16_t readNextBits(uint8_t numBits) + { + uint16_t bits = 0; + bitreader.read(numBits, &bits); + return bits; + } + + void filterToneFunctions(ra::strings::StringVector & calls) + { + ra::strings::StringVector copy = calls; + calls.clear(); + + for(size_t i=0; icurrent_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + //play the actual content + anyrtttl::nonblocking::begin(PIEZO_PIN, tetris); + while( !anyrtttl::nonblocking::done() ) + { + anyrtttl::nonblocking::play(); + } + + //assert + + //load both files into memory + ra::strings::StringVector expectedCalls; + ASSERT_TRUE (ra::testing::GetTextFileContent("expected_call_stack.log", expectedCalls)); + ra::strings::StringVector actualCalls; + ASSERT_TRUE (ra::testing::GetTextFileContent(logFile.c_str(), actualCalls)); + + //assert that file ends with a noTone(); + std::string lastCall = actualCalls[actualCalls.size()-1]; + ASSERT_NE(lastCall.find("noTone("), std::string::npos); + + //compare tone() function calls with the "blocking" template + //file since its the only common thing betwwen blocking and + //non-blocking api. + + //filter out anything that is not tone(...) + filterToneFunctions(expectedCalls); + filterToneFunctions(actualCalls); + + ASSERT_EQ( expectedCalls.size(), actualCalls.size() ); + for(size_t i=0; icurrent_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + anyrtttl::nonblocking::begin(PIEZO_PIN, tetris); + + //play for 5 sec then stop. + //note: this is a blocking code section + //use to demonstrate the use of stop() + unsigned long start = millis(); + while( millis() - start < 5000 ) + { + anyrtttl::nonblocking::play(); + } + anyrtttl::nonblocking::stop(); + + //assert + + //load output file into memory + ra::strings::StringVector actualCalls; + ASSERT_TRUE (ra::testing::GetTextFileContent(logFile.c_str(), actualCalls)); + + //assert that file ends with a noTone(); + std::string lastCall = actualCalls[actualCalls.size()-1]; + ASSERT_NE(lastCall.find("noTone("), std::string::npos); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestAnyRtttl, testBlockingPlay) + { + //setup log file + std::string logFile = std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + //play the actual content + anyrtttl::blocking::play(PIEZO_PIN, tetris); + + //assert + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\n", "") ); + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\r\n", "") ); //windows + std::string diffReason; + bool fileAreIdentical = ra::testing::IsFileEquals("expected_call_stack.log", logFile.c_str(), diffReason, 1); + ASSERT_TRUE( fileAreIdentical ) << diffReason.c_str(); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestAnyRtttl, testBlockingPlay10) + { + //setup log file + std::string logFile = std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + //play the actual content + bitreader.setBuffer(tetris10); + anyrtttl::blocking::play10Bits(PIEZO_PIN, tetris10_length, &readNextBits); + + //assert + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\n", "") ); + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\r\n", "") ); //windows + std::string diffReason; + bool fileAreIdentical = ra::testing::IsFileEquals("expected_call_stack.log", logFile.c_str(), diffReason, 1); + ASSERT_TRUE( fileAreIdentical ) << diffReason.c_str(); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestAnyRtttl, testBlockingPlay16) + { + //setup log file + std::string logFile = std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + //play the actual content + anyrtttl::blocking::play16Bits(PIEZO_PIN, tetris16, tetris16_length); + + //assert + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\n", "") ); + ASSERT_TRUE( fileContentReplace(logFile.c_str(), "millis();\r\n", "") ); //windows + std::string diffReason; + bool fileAreIdentical = ra::testing::IsFileEquals("expected_call_stack.log", logFile.c_str(), diffReason, 1); + ASSERT_TRUE( fileAreIdentical ) << diffReason.c_str(); + } + //-------------------------------------------------------------------------------------------------- + static int toneCounts = 0; + static int noToneCounts = 0; + static int delayCounts = 0; + void myToneFunc(uint8_t pin, unsigned int frequency, unsigned long duration) + { + toneCounts++; + } + void myNoToneFunc(uint8_t pin) + { + noToneCounts++; + } + void myDelayFunc(unsigned long duration) + { + delayCounts++; + } + + TEST_F(TestAnyRtttl, testCustomFunctionsPlay) + { + //setup log file + std::string logFile = std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + ".log"; + testarduino::setLogFile(logFile.c_str()); + + toneCounts = 0; + noToneCounts = 0; + delayCounts = 0; + + //use test's custom functions + anyrtttl::setToneFunction(&myToneFunc); + anyrtttl::setNoToneFunction(&myNoToneFunc); + anyrtttl::setDelayFunction(&myDelayFunc); + + //play the actual content + anyrtttl::blocking::play(PIEZO_PIN, tetris); + + //back to native arduino functions + anyrtttl::setToneFunction(&tone); + anyrtttl::setNoToneFunction(&noTone); + anyrtttl::setDelayFunction(&delay); + + //assert + ASSERT_GT( toneCounts , 0 ); + ASSERT_GT( noToneCounts, 0 ); + ASSERT_GT( delayCounts , 0 ); + } + //-------------------------------------------------------------------------------------------------- +} // End namespace test +} // End namespace arduino diff --git a/lib/AnyRtttl/test/TestAnyRtttl.h b/lib/AnyRtttl/test/TestAnyRtttl.h new file mode 100644 index 0000000000..8fdd41183a --- /dev/null +++ b/lib/AnyRtttl/test/TestAnyRtttl.h @@ -0,0 +1,25 @@ +// --------------------------------------------------------------------------- +// AUTHOR/LICENSE: +// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file. +// The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl +// MIT License: http://www.opensource.org/licenses/mit-license.php +// --------------------------------------------------------------------------- + +#ifndef TEST_RTTTL_H +#define TEST_RTTTL_H + +#include + +namespace arduino { namespace test +{ + class TestAnyRtttl : public ::testing::Test + { + public: + virtual void SetUp(); + virtual void TearDown(); + }; + +} // End namespace test +} // End namespace arduino + +#endif // TEST_RTTTL_H diff --git a/lib/AnyRtttl/test/expected_call_stack.log b/lib/AnyRtttl/test/expected_call_stack.log new file mode 100644 index 0000000000..5fc4e6e92a --- /dev/null +++ b/lib/AnyRtttl/test/expected_call_stack.log @@ -0,0 +1,124 @@ +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,988,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,1175,187); +delay(188); +noTone(99); +tone(99,1319,93); +delay(94); +noTone(99); +tone(99,1175,93); +delay(94); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,988,187); +delay(188); +noTone(99); +tone(99,880,375); +delay(376); +noTone(99); +tone(99,880,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,1175,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,988,375); +delay(376); +noTone(99); +tone(99,988,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,1175,375); +delay(376); +noTone(99); +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,1047,375); +delay(376); +noTone(99); +tone(99,880,375); +delay(376); +noTone(99); +tone(99,880,750); +delay(751); +noTone(99); +delay(187); +tone(99,1175,375); +delay(376); +noTone(99); +tone(99,1397,187); +delay(188); +noTone(99); +tone(99,1760,375); +delay(376); +noTone(99); +tone(99,1568,187); +delay(188); +noTone(99); +tone(99,1397,187); +delay(188); +noTone(99); +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,1319,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,1175,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,988,375); +delay(376); +noTone(99); +tone(99,988,187); +delay(188); +noTone(99); +tone(99,1047,187); +delay(188); +noTone(99); +tone(99,1175,375); +delay(376); +noTone(99); +tone(99,1319,375); +delay(376); +noTone(99); +tone(99,1047,375); +delay(376); +noTone(99); +tone(99,880,375); +delay(376); +noTone(99); +tone(99,880,375); +delay(376); +noTone(99); diff --git a/lib/AnyRtttl/test/main.cpp b/lib/AnyRtttl/test/main.cpp new file mode 100644 index 0000000000..c3b44b2c8d --- /dev/null +++ b/lib/AnyRtttl/test/main.cpp @@ -0,0 +1,26 @@ +// --------------------------------------------------------------------------- +// AUTHOR/LICENSE: +// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file. +// The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl +// MIT License: http://www.opensource.org/licenses/mit-license.php +// --------------------------------------------------------------------------- + +#include +#include +#include "rapidassist/environment.h" + +int main(int argc, char **argv) +{ + //define default values for xml output report + if (ra::environment::IsConfigurationDebug()) + ::testing::GTEST_FLAG(output) = "xml:anyrtttl_unittest.debug.xml"; + else + ::testing::GTEST_FLAG(output) = "xml:anyrtttl_unittest.release.xml"; + + ::testing::GTEST_FLAG(filter) = "*"; + ::testing::InitGoogleTest(&argc, argv); + + int wResult = RUN_ALL_TESTS(); //Find and run all tests + + return wResult; // returns 0 if all the tests are successful, or 1 otherwise +} diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 00d228573b..8d7b8766d9 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -227,7 +227,9 @@ // #define FEATURE_I2C_DEVICE_CHECK 0 // Disable the I2C Device check feature // #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching the I2C address from I2C plugins. Will be enabled when FEATURE_I2C_DEVICE_CHECK is enabled // #define FEATURE_RTTTL 1 // Enable rtttl command - +// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling +// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode +// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense #if FEATURE_CUSTOM_PROVISIONING // For device models, see src/src/DataTypes/DeviceModel.h diff --git a/src/src/Commands/GPIO.cpp b/src/src/Commands/GPIO.cpp index ef87046003..17c5319704 100644 --- a/src/src/Commands/GPIO.cpp +++ b/src/src/Commands/GPIO.cpp @@ -97,8 +97,8 @@ bool gpio_monitor_helper(int port, struct EventStruct *event, const char *Line) if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat( - logPrefix, - strformat(F(" port #%d: added to monitor list."), port))); + logPrefix, + strformat(F(" port #%d: added to monitor list."), port))); } String dummy; SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, dummy, 0); @@ -143,7 +143,7 @@ bool gpio_unmonitor_helper(int port, struct EventStruct *event, const char *Line removeMonitorFromPort(key); if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat( - logPrefix, + logPrefix, strformat(F(" port #%d: removed from monitor list."), port))); } @@ -385,12 +385,23 @@ const __FlashStringHelper * Command_GPIO_RTTTL(struct EventStruct *event, const // play a tune via a RTTTL string, look at https://www.letscontrolit.com/forum/viewtopic.php?f=4&t=343&hilit=speaker&start=10 for // more info. + // First assume 'old' syntax: rtttl, + // No comma between the GPIO argument and the melody String melody = parseStringToEndKeepCase(Line, 2); + if (melody.indexOf(':') == -1) { + // Apparently this is now using the 'new' (correct) syntax: + // rtttl,, + melody = parseStringToEndKeepCase(Line, 3); + } melody.replace('-', '#'); + melody.replace('_', '#'); if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("RTTTL: pin: %d melody: %s"), event->Par1, melody.c_str())); } + #if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC + set_rtttl_melody(melody); + #endif // if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC if (play_rtttl(event->Par1, melody.c_str())) { return return_command_success_flashstr(); @@ -488,7 +499,7 @@ const __FlashStringHelper * Command_GPIO_Toggle(struct EventStruct *event, const String log = logPrefix; log += concat( - F(" toggle"), + F(" toggle"), strformat(F(": port#%d: set to %d"), event->Par1, static_cast(!state))); addLog(LOG_LEVEL_ERROR, log); SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); @@ -1145,7 +1156,6 @@ bool gpio_mode_range_helper(uint8_t pin, uint8_t pinMode, struct EventStruct *ev createAndSetPortStatus_Mode_State(key, mode, currentState); - String log = logPrefix; log += strformat(F(" : port#%d: MODE set to "), pin); log += logPostfix; @@ -1302,7 +1312,7 @@ bool getGPIOPinStateValues(String& str) { str = String(tempValue); break; } - #endif +#endif default: addLog(LOG_LEVEL_ERROR, F("PLUGIN PINSTATE. Plugin not included in build")); return false; diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 98897d01d9..8e5333fec7 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -2920,6 +2920,18 @@ To create/register a plugin, you have to : #undef FEATURE_RTTTL #define FEATURE_RTTTL 1 #endif +#if FEATURE_RTTTL && !defined(FEATURE_ANYRTTTL_LIB) + #define FEATURE_ANYRTTTL_LIB 1 // Enable AnyRtttl library +#endif +#ifndef FEATURE_ANYRTTTL_LIB + #define FEATURE_ANYRTTTL_LIB 0 +#endif +#ifndef FEATURE_ANYRTTTL_ASYNC + #define FEATURE_ANYRTTTL_ASYNC 1 // Use Async by default for better (non-blocking) behavior +#endif +#if FEATURE_ANYRTTTL_ASYNC && !defined(FEATURE_RTTTL_EVENTS) + #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense +#endif #ifndef FEATURE_SD #define FEATURE_SD 0 diff --git a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp index 80d6c64c2b..1ad7a88cea 100644 --- a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp +++ b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp @@ -9,6 +9,9 @@ #include "../Globals/NetworkState.h" #include "../Globals/Services.h" #include "../Globals/Settings.h" +#if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC +#include "../Helpers/Audio.h" +#endif // if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC #include "../Helpers/ESPEasy_time_calc.h" #include "../Helpers/Network.h" #include "../Helpers/Networking.h" @@ -121,6 +124,10 @@ void backgroundtasks() delay(0); + #if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC + update_rtttl(); + #endif // if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC + statusLED(false); runningBackgroundTasks = false; diff --git a/src/src/Helpers/Audio.cpp b/src/src/Helpers/Audio.cpp index a7b3c6c831..f9509b4995 100644 --- a/src/src/Helpers/Audio.cpp +++ b/src/src/Helpers/Audio.cpp @@ -1,5 +1,6 @@ #include "../Helpers/Audio.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" #include "../Globals/RamTracker.h" #include "../Helpers/Hardware.h" @@ -8,58 +9,152 @@ Generate a tone of specified frequency on pin \*********************************************************************************************/ bool tone_espEasy(int8_t _pin, unsigned int frequency, unsigned long duration) { - if (_pin<0) return false; + if (!validGpio(_pin)) { return false; } // Duty cycle can be used as some kind of volume. - if (!set_Gpio_PWM_pct(_pin, 50, frequency)) return false; + if (!set_Gpio_PWM_pct(_pin, 50, frequency)) { return false; } + if (duration > 0) { delay(duration); return set_Gpio_PWM(_pin, 0, frequency); } return true; - } /********************************************************************************************\ Play RTTTL string on specified pin \*********************************************************************************************/ #if FEATURE_RTTTL +# if FEATURE_ANYRTTTL_LIB +# include +# include +# if FEATURE_RTTTL_EVENTS +# include "../Globals/EventQueue.h" +# include "../Globals/Settings.h" +static bool rtttlPlaying = false; +# endif // if FEATURE_RTTTL_EVENTS +# if FEATURE_ANYRTTTL_ASYNC +static String rtttlMelody; + +void clear_rtttl_melody() { + // The non-blocking play will read from a char pointer. + // So we must stop the playing before changing the string as it could otherwise lead to a crash. + anyrtttl::nonblocking::stop(); + rtttlMelody = String(); +} + +void set_rtttl_melody(String& melody) { + clear_rtttl_melody(); + rtttlMelody = melody; +} + +# endif // if FEATURE_ANYRTTTL_ASYNC + + +bool play_rtttl(int8_t _pin, const char *p) { + if (!validGpio(_pin)) { return false; } + + // addLog(LOG_LEVEL_INFO, F("RTTTL: Using AnyRtttl")); + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + anyrtttl::setNoToneFunction(&setInternalGPIOPullupMode); + # if FEATURE_ANYRTTTL_ASYNC + + if (anyrtttl::nonblocking::isPlaying()) { // If currently playing, cancel that + addLog(LOG_LEVEL_INFO, F("RTTTL: Cancelling running song...")); + anyrtttl::nonblocking::stop(); + # if FEATURE_RTTTL_EVENTS + + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Cancelled")); + } + rtttlPlaying = false; + # endif // if FEATURE_RTTTL_EVENTS + } + + if (!rtttlMelody.isEmpty()) { + anyrtttl::nonblocking::begin(_pin, rtttlMelody.c_str()); + } else { + anyrtttl::nonblocking::begin(_pin, p); + } + anyrtttl::nonblocking::play(); + # if FEATURE_RTTTL_EVENTS + + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Started")); + } + rtttlPlaying = true; + # endif // if FEATURE_RTTTL_EVENTS + # else // if FEATURE_ANYRTTTL_ASYNC + anyrtttl::blocking::play(_pin, p); + # endif // if FEATURE_ANYRTTTL_ASYNC + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl2")); + # endif // ifndef BUILD_NO_RAM_TRACKER + return true; +} + +# if FEATURE_ANYRTTTL_ASYNC +void update_rtttl() { + if (anyrtttl::nonblocking::isPlaying()) { + anyrtttl::nonblocking::play(); + } else { + # if FEATURE_RTTTL_EVENTS + + if (rtttlPlaying) { + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Finished")); + } + rtttlPlaying = false; + } + # endif // if FEATURE_RTTTL_EVENTS + clear_rtttl_melody(); // Release memory + } +} + +# endif // if FEATURE_ANYRTTTL_ASYNC + +# else // if FEATURE_ANYRTTTL_LIB bool play_rtttl(int8_t _pin, const char *p) { - if (_pin<0) return false; - - #ifndef BUILD_NO_RAM_TRACKER + if (!validGpio(_pin)) { return false; } + + # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("play_rtttl")); - #endif - #define OCTAVE_OFFSET 0 + # endif // ifndef BUILD_NO_RAM_TRACKER + # define OCTAVE_OFFSET 0 // FIXME: Absolutely no error checking in here const int notes[] = { 0, - 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, - 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, - 1047,1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, - 2093,2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951 + 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, + 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, + 1047,1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, + 2093,2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951 }; uint8_t default_dur = 4; uint8_t default_oct = 6; - int bpm = 63; - int num; - long wholenote; - long duration; + int bpm = 63; + int num; + long wholenote; + long duration; uint8_t note; uint8_t scale; // format: d=N,o=N,b=NNN: // find the start (skip name, etc) - while (*p != ':') { + while (*p != ':') { p++; // ignore name - if (*p == 0) return false; + + if (*p == 0) { return false; } } - p++; // skip ':' + p++; // skip ':' // get default duration if (*p == 'd') @@ -191,9 +286,12 @@ bool play_rtttl(int8_t _pin, const char *p) delay(duration / 10); } } - #ifndef BUILD_NO_RAM_TRACKER + setInternalGPIOPullupMode(_pin); // Turn off sound, Arduino _noTone() doesn't do that reliably + # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("play_rtttl2")); - #endif + # endif // ifndef BUILD_NO_RAM_TRACKER return true; } + +# endif // if FEATURE_ANYRTTTL_LIB #endif // if FEATURE_RTTTL diff --git a/src/src/Helpers/Audio.h b/src/src/Helpers/Audio.h index 297179d3d6..bff26501a7 100644 --- a/src/src/Helpers/Audio.h +++ b/src/src/Helpers/Audio.h @@ -7,7 +7,7 @@ /********************************************************************************************\ Generate a tone of specified frequency on pin \*********************************************************************************************/ -bool tone_espEasy(int8_t _pin, +bool tone_espEasy(int8_t _pin, unsigned int frequency, unsigned long duration); @@ -15,9 +15,14 @@ bool tone_espEasy(int8_t _pin, Play RTTTL string on specified pin \*********************************************************************************************/ #if FEATURE_RTTTL -bool play_rtttl(int8_t _pin, +bool play_rtttl(int8_t _pin, const char *p); +# if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC +void update_rtttl(); +void clear_rtttl_melody(); +void set_rtttl_melody(String& melody); +# endif // if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC #endif // if FEATURE_RTTTL -#endif \ No newline at end of file +#endif // ifndef HELPERS_AUDIO_H